IT-Consultant: Frederick J. Harris > Discussion

c++ client PowerBASIC Server

(1/9) > >>

James C. Fuller:
Fred,
  I decided to move to discussion as it really has no bearing on your COM Memory exploration.
I'm just experimenting and have a very poor knowledge of c++ so I thought I'd put this code here for you to critique.
I too have been reading (and reading and reading and....)Inside COM. I thought your examples looked a bit familiar. ;D
I can't believe all the c++ code needed to create a server!
I did get the client to compile using Code::Blocks and gcc 3.4.5. I had to add liboleaut32.a and libuuid.a to the Build Options
I read that gcc's COM support is not that hot??
I much prefer RadASM with BCC++ 5.5.
Here's another example of a c++ client PowerBASIC server. Note the use of my PROPERTY Macros.

James

Edit: Had to change a couple UNIT to DWORD so gcc would compile.

c++ Client

--- Code: ---#include <windows.h>
#include <oleauto.h>     
#include <stdio.h>

static const CLSID CLSID_cDisplayOpenFileGuid = {0x1b0f486f, 0x9cca, 0x47ba,{ 0xa3, 0x38, 0x8f, 0x71, 0x46, 0xb7, 0x3c, 0x26}};
static const IID IID_iDisplayOpenFileGuid = {0xb72cdd37, 0x5349, 0x46b7,{ 0x8a, 0x27, 0x59, 0x2d, 0x23, 0xb3, 0x4f, 0xe3}};

HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);

interface iDisplayOpenFile : IUnknown
{
virtual void __stdcall put_Folder(BSTR);
virtual void __stdcall put_Filter(BSTR);
virtual void __stdcall put_hParent(DWORD);
virtual void __stdcall put_DflExt(BSTR);
virtual void __stdcall put_Start(BSTR);
virtual void __stdcall put_Title(BSTR);
virtual void __stdcall put_Flags(DWORD);
virtual BSTR __stdcall GetName();
};

int main(void)
{
  iDisplayOpenFile* pDof=NULL;
  HRESULT hr;
  IClassFactory*  pCF=NULL;
  HMODULE         hDll=NULL;
  BSTR str1;
// change here for a starting Folder 
  char stemp[]="D:\\Borland\\BCC55\\Include\\Sys";
 
//Change Path Here

  hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBCTEST03.DLL");
 
  ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");

  hr=ptrDllGetClassObject(CLSID_cDisplayOpenFileGuid,IID_IClassFactory,(void**)&pCF);
  if(SUCCEEDED(hr))
  {

    pCF->CreateInstance(NULL,IID_iDisplayOpenFileGuid,(void**)&pDof);
    // Release Class Factory
    pCF->Release();
    // Create a BSTR using char stemp
    str1 = SysAllocStringByteLen(stemp,strlen(stemp));
    // Set the starting Folder
    pDof->put_Folder(str1);
    // Free up the string
    SysFreeString(str1);
    //Show OpenFile Dialog and retrieve Selected Files
    str1 = pDof->GetName();
    printf("str1 = %s\n",str1);
    // Release pDof
    hr=pDof->Release();
  }
FreeLibrary(hDll);
 
  getchar();
  return 0;

}   



--- End code ---

PowerBASIC Server

--- Code: ---'SED_PBWIN
#COMPILE DLL "PBCTEST03.DLL"
#DIM ALL
#COM TLIB ON
#INCLUDE ONCE "WIN32API.INC"
MACRO PropGet(PropName,PropType)=PROPERTY GET PropName() AS PropType:PROPERTY=PropName:END PROPERTY
MACRO PropSet(PropName,PropType)=PROPERTY SET PropName(BYVAL param AS PropType):PropName=param:END PROPERTY


$cDisplayOpenFileGuid = GUID$("{1B0F486F-9CCA-47ba-A338-8F7146B73C26}")
$iDisplayOpenFileGuid = GUID$("{B72CDD37-5349-46b7-8A27-592D23B34FE3}")
'GUID's create with GUIDGEN
'// {1B0F486F-9CCA-47ba-A338-8F7146B73C26}
'DEFINE_GUID(<<name>>,
'0x1b0f486f, 0x9cca, 0x47ba, 0xa3, 0x38, 0x8f, 0x71, 0x46, 0xb7, 0x3c, 0x26);
'// {B72CDD37-5349-46b7-8A27-592D23B34FE3}
'DEFINE_GUID(<<name>>,
'0xb72cdd37, 0x5349, 0x46b7, 0x8a, 0x27, 0x59, 0x2d, 0x23, 0xb3, 0x4f, 0xe3);

CLASS cDisplayOpenFile $cDisplayOpenFileGuid AS COM
    INSTANCE sFileName,Folder,Filter,Start,DflExt,Title AS STRING
    INSTANCE hParent,Flags AS LONG

    CLASS METHOD CREATE
        Filter = CHR$("All Files",0,"*.*",0)
        Flags = %OFN_EXPLORER OR %OFN_FILEMUSTEXIST
        Title = "Select File"
        Start = ""
        Folder = CURDIR$
STDOUT "CREATE"
    END METHOD

    INTERFACE iDisplayOpenFile $iDisplayOpenFileGuid : INHERIT IUNKNOWN

PropSet(Folder,STRING)
        PropSet(Filter,STRING)
        PropSet(hParent,DWORD)
        PropSet(DflExt,STRING)
        PropSet(Start,STRING)
        PropSet(Title,STRING)
        PropSet(Flags,DWORD)
        METHOD GetName() AS STRING
            DISPLAY  OPENFILE hParent,,,Title,Folder,Filter,Start,DflExt,Flags TO sFileName
            METHOD = sFileName
        END METHOD
    END INTERFACE
END CLASS


'==============================================================================
'MCM code
'------------------------------------------------------------------------------
 FUNCTION STDOUT (Z AS STRING) AS LONG
 ' returns TRUE (non-zero) on success

   LOCAL hStdOut AS LONG, nCharsWritten AS LONG
   LOCAL w AS STRING


   hStdOut      = GetStdHandle (%STD_OUTPUT_HANDLE)
   IF hSTdOut   = -1&  or hStdOut = 0&  THEN     ' invalid handle value, coded in line to avoid
                                                 ' casting differences in Win32API.INC
                                                 ' %NULL test added for Win/XP
     AllocConsole
     hStdOut  = GetStdHandle (%STD_OUTPUT_HANDLE)
   END IF
   w = Z & $CRLF
   FUNCTION = WriteFile(hStdOut, BYVAL STRPTR(W), LEN(W),  nCharsWritten, BYVAL %NULL)


 END FUNCTION
'==============================================================================

--- End code ---

Frederick J. Harris:
Hi James!

     I'm stuck on the top command in the dll - '#COM TLIB ON'.  My compiler doesn't seem to like that and I don't understand it either even though I've looked through help.  What does that command do and how should I set up my environment to get that to compile?

     Oh, by the way.  Here is the thought or observation that just more or less forced itself upon me on Friday night.  Actually, I had noted it before but tried to forget it do to its unpleasantness.  Take a look at any of the tabular outputs from the many programs I presented that dumps memory as it iterates through the VTables printing function addresses, and take note of the fact that the addresses of QueryInterface(), AddRef() and Release() in each VTable (either I_X or I_Y) don't point to the single implementation of these functions in class CA!  Indeed, when any of these functions get called through the addresses in the respective VTable, we get an output message that the single implementation of that function was called, even though the numbers in the VTable don't match!

     Do you have a plausible explanation for this?  I really believe its a C++ issue rather than a PowerBASIC or COM issue.  Dale Rogerson kind or skirts around the issue in chapter 3 of his book Inside COM when he discusses QueryInterface() and casting with reference to Multiple Inheritance. 

     So what I'm saying is that I think the whole issue concerning memory layout would be 'tied neatly with a red bow' in my mind, in other words would set a good bit better with me, if in the function pointer slots of each VTable for the IUnknown functions the numbers would point to a single implementation of these functions, because, after all, even though many VTables are possible, IUnknown only has one implementation.

Frederick J. Harris:
Hi Again James!

     I've been a bit remiss in following up on your posts/code so I decided to start at the top.  The 1st one worked fine, but I'm getting crashes on the one where you created the dll with PowerBASIC and set your name 'James' to the s1 variable in the PB Constructor.  Its this one...

#COMPILE DLL "PBCTEST01.DLL"   

     When I run the C++ program I get as far as

     hr=piTest01->S1_Get(&str1);

     ...and then a crash.  Here is the last output I get...


--- Code: ---ptrDllGetClassObject = 9983009
Constructor Called #1
Called Fx1 iNum = 25
Called Fx2 iNum = 50
Called iNum2_Get = 17
iNum2 = 17
Called iNum2_Set = 22
Called iNum2_Get = 22
iNum2 = 22

--- End code ---

     So I expect there may be something wrong there.  Is that running to a successful completion for you?

     Also, I noted in the PB Dll code that you were passing the String Method parameters Byref...


--- Code: ---METHOD S1_Get(Param AS STRING)
      Param = S1
END METHOD
METHOD S1_Set(Param AS STRING)
      S1 = Param
      STDOUT "Called S1_Set -> "+ S1
END METHOD

--- End code ---

     I thought I read somewhere in the PB Docs that string parameters to methods should be Byval, or am I wrong there or doesn't it matter?  I don't know that's why I'm asking?  I remember ruminating on the issue for a bit when the compiler 1st came out, and ended up thinking Byval would be the way to do it because the instance of a class needs to have its own copy of the whole string, not a reference to one somewhere else? 

     I really need to learn more about the BSTR type.  I used it some on the work I was doing a month or two ago with Typelibs, but that was my 1st use of it.       

James C. Fuller:
Fred,
  I confess I have not examined your memory tests too close. That goes a little deeper than I care to venture.
I'm having a hard enough time getting a superficial grasp on COM to the point so it works  :)


Ok, to make sure we are on the same page as regard to my examples. The ones in the other thread were experiments and I may have changed them since.

Re: #COM TLIB ON
  It should compile with 9.00? It's not needed.

I changed to BYVAL on all Property Sets and will continue to use it from now on.

I used code::Blocks with gcc(g++) 3.4.5 from the mingw setup to compile. I noticed the above OpenFile code was only
a 6k exe with gcc where the bc++ was 53k ??? There may be debug code in the bcc one. I'm not all that familiar with RadASM.


I think the void should probably be HRESULT now that I'm using properties in the server??

I am not really using BSTR as a true BSTR just as a receptacle that will compile and work with PB strings.
True BSTR are unicode.


The thing I like is using PB with my PropGet/Set's and having the code accessable rather easily from c++.
I did try to get it to work with Peles c but very little luck in that department.

James


c++ client


--- Code: ---// Compiled with g++ 3.4.5
#include <windows.h>
#include <oleauto.h>
#include <stdio.h>
static const CLSID CLSID_cTest01   = {0xeedfbd9e, 0x8cd7, 0x43ae,{0x8b, 0x58, 0x66, 0x76, 0xee, 0x2b, 0x1e, 0x72}};
static const IID   IID_iTest01    = {0x7e58490e, 0x8445, 0x4b61,{0x9c, 0xed, 0x61, 0x18, 0x46, 0xf8, 0x35, 0xd7}};



HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);

interface iTest1 : IUnknown
{
 virtual HRESULT __stdcall Fx1(int)=0;
 virtual HRESULT __stdcall Fx2(int)=0;
 virtual long __stdcall get_iNum2();
 virtual void __stdcall put_iNum2(int);
 virtual BSTR __stdcall get_S1();
 virtual void __stdcall put_S1(BSTR);
};




int main(void)
{
  iTest1* piTest01=NULL;
  HRESULT hr;
  IClassFactory*  pCF=NULL;
  HMODULE         hDll=NULL;

  int iNum2,iNum3=9;
  BSTR str1;
  char stemp[]="Fuller";

//Change Path Here

  hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBCTEST01.DLL");

  ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");

  hr=ptrDllGetClassObject(CLSID_cTest01,IID_IClassFactory,(void**)&pCF);
  if(SUCCEEDED(hr))
  {
    pCF->CreateInstance(NULL,IID_iTest01,(void**)&piTest01);
    // Release Class Factory
    pCF->Release();

    hr=piTest01->Fx1(25);
    hr=piTest01->Fx2(50);
    iNum2 = piTest01->get_iNum2();
    printf("iNum2 = %u\n",iNum2);
    piTest01->put_iNum2(iNum3);
    iNum2 = piTest01->get_iNum2();
    printf("iNum2 = %u\n",iNum2);
    str1 = piTest01->get_S1();
    printf("str1 = %s\n",str1);
    str1 = SysAllocStringByteLen(stemp,strlen(stemp));
    piTest01->put_S1(str1);
    SysFreeString(str1);
    str1 = piTest01->get_S1();
    printf("str1 = %s\n",str1);

// Added 01-27-09
// Need to free all string memory
    SysFreeString(str1);
    hr=piTest01->Release();

  }


 FreeLibrary(hDll);

 getchar();
 return 0;

}




--- End code ---

PbServer

--- Code: ---'SED_PBWIN
#COMPILE DLL "PBCTEST01.DLL"
#DIM ALL
#INCLUDE ONCE "WIN32API.INC"
$CLSID_cTest01 =GUID$("{EEDFBD9E-8CD7-43ae-8B58-6676EE2B1E72}")
$IID_iTest1 = GUID$("{7E58490E-8445-4b61-9CED-611846F835D7}")
MACRO PropGet(PropName,PropType)=PROPERTY GET PropName() AS PropType:PROPERTY=PropName:END PROPERTY
MACRO PropSet(PropName,PropType)=PROPERTY SET PropName(BYVAL param AS PropType):PropName=param:END PROPERTY


CLASS cTest01 $CLSID_cTest01 AS COM
INSTANCE S1 AS STRING, iNum2 AS LONG
CLASS METHOD CREATE
STDOUT "Constructor Called #1"
iNum2 = 17
S1 = "James"
END METHOD
CLASS METHOD DESTROY
STDOUT "Destructor Called"
END METHOD
INTERFACE iTest1 $IID_iTest1 : INHERIT IUNKNOWN
METHOD Fx1(BYVAL iNum AS LONG)
STDOUT "Called Fx1 iNum = " + FORMAT$(iNum)
END METHOD

METHOD Fx2(BYVAL iNum AS LONG)
STDOUT "Called Fx2 iNum = " + FORMAT$(iNum)
END METHOD

PropGet(iNum2,LONG)
PropSet(iNum2,LONG)


PropGet(S1,STRING)
PropSet(S1,STRING)


END INTERFACE
END CLASS



'==============================================================================
'MCM code
'------------------------------------------------------------------------------
 FUNCTION STDOUT (Z AS STRING) AS LONG
 ' returns TRUE (non-zero) on success

   LOCAL hStdOut AS LONG, nCharsWritten AS LONG
   LOCAL w AS STRING


   hStdOut      = GetStdHandle (%STD_OUTPUT_HANDLE)
   IF hSTdOut   = -1&  or hStdOut = 0&  THEN     ' invalid handle value, coded in line to avoid
                                                 ' casting differences in Win32API.INC
                                                 ' %NULL test added for Win/XP
     AllocConsole
     hStdOut  = GetStdHandle (%STD_OUTPUT_HANDLE)
   END IF
   w = Z & $CRLF
   FUNCTION = WriteFile(hStdOut, BYVAL STRPTR(W), LEN(W),  nCharsWritten, BYVAL %NULL)


 END FUNCTION
'==============================================================================

--- End code ---

Frederick J. Harris:
Good job James!  I got it to work and will study your code.  I'm just really looking close at the whole BSTR, OLECHAR thing now.  Certainly strings are important as it would be hard for me to write a program without them!

The thing with the memory is one of those things I'm going to have to put aside until I gather more knowledge on C++ because I suspect the issue goes right to the heart of what the C++ compiler does to make multiple inheritance work.  I've a book on it by Stanley Lippmann named 'The C++ Object Model' but its unfortunately over my head as of yet.  I skimmed through one of the chapters yesterday that seems to be discussing the issue I'm raising, and its unbelievably complex.  It seems there are more tables in memory containing other information beyond what I found, and I'm rather sure within those other tables the final resolution is made that causes the single implementation of the IUnknown functions to be called.

I watch the sizes of executables very close too, and was astounded to see how small the CodeBlocks generated executables were.  That is the 1st instance where I actually found a C or C++ compiler to generate exes smaller than PowerBASIC's.  However, just a few weeks ago I downloaded and installed Microsoft's VC++ 9 (Visual Studio 2008) and its executables are about like CodeBlocks (or perhaps I should say the other way around).  So its my guess there is system code not being statically linked into the newer exes.  I'd be interested if anyone knew the details on this.

You had mentioned that you were astounded by the quantity of C++ code necessary to create a COM server.  If you are basing your comment on the fairly voluminous code bulk of my little test class CA, I can assure you it gets considerably worse than that as that example - while a valid COM server - lacked the code to deal with creating a type lib.  In C++ one does that by 1sr creating a *.idl file and compiling that with midl (Microsoft Interface Definition Language Compiler).  That generates several more files that have to be compiled and linked into the dll.  And if you want to see some nasty looking C code you'll find it in those files as they contain  a lot of proxy-stubb marshalling code to allow the COM server to function transparently across a network.

The reason I know as much as I do about that end of it is I spent a lot of time in Guy and Henry Eddon's book "Inside Distributed COM' (which I found three or four years ago in a used bookstore for about $2.00 - CD and all ), and its a pretty good book.  They start out in that book with IDL, as opposed to Dale Rogerson's book which doesn't introduce it to near the end.

Right at the moment I'm trying to work through "Developer's Workshop to COM and ATL 3.0" by Andrew W. Troelson.  Its pretty much consuming my time.

Navigation

[0] Message Index

[#] Next page

Go to full version