Com Tutorial #1: Component Object Model Memory Layout
I have many books on COM and they all have pictures of the memory layout Microsoft's Component Object Model uses involving virtual function table pointers and the virtual function table itself. The pictures with their little boxes and arrows pointing about are helpful but left me somewhat dissatisfied that I fully comprehended what exactly was going on with this material. The problem is further compounded and confused by the fact that all my books use C++ in describing the workings of COM, and that language effectively hides quite a bit of what is really taking place in memory. I've fought the thing for quite a while and finally developed several programs that for me at least have been quite helpful in seeing what is actually going on. I will present them shortly in the hope they may be useful to you also. If you are trying to master COM two books that I have found useful are "Inside COM" by Dale Rogerson, and "Inside DCOM" by Guy and Henry Eddon. The latter book covers connection points in some detail. If you attempt to get these books make sure you get the CDs with them as you will need the source code.
Returning to the topic though, my point concerning the difficulties of comprehending what is going on in memory with COM objects is something like the following. Let us take a simple integer array that holds five elements, say – five 32 bit PowerBASIC longs. It really isn't hard to completely and exhaustively describe the exact nature of this data structure in memory. The compiler must allocate space for five longs and that will require 20 bytes. It will obtain these 20 bytes and perhaps a few extra for good measure (due to memory allocation granularity) from the operating system, and in the process of doing that will obtain the base address of this memory. It is into that memory that we can store our five longs and we can access them through subscripts and the symbol we told PowerBASIC we want associated with the array. We can easily output both the contents of this array and the various memory addresses of the individual elements.
Well, what about a COM Object? Can't we describe it in exactly the same terms? As a matter of fact we can, but it's one heck of a lot trickier. Following are some specific issues with COM objects that don't have any real close analogies with simple arrays...
1) COM objects are created by Api function calls to the operating system, not simple memory allocations;
2) COM objects involve multiple memory allocations of various sizes that occur in various places in memory – not just in one place;
3) COM objects consist of loads of pointers, i.e., pointers to the class object, pointers to V Tables, pointers to functions, etc.
To help accomplish what I wanted to do I thought it best to create my own simple COM object using C++ and use that object in my deconstruction work of finding out where things are at in memory. If I created a simple COM object with PowerBASIC, I'd be able to call methods through function pointers as I did in a demo on ObjPtr I posted in PowerBASIC Source Code some time ago....
http://www.powerbasic.com/support/pbforums/showthread.php?t=38544
...but I wouldn't be able to fool around with QueryInterface or any of the other unique COM functions because their implementation would have been internal to the PowerBASIC binaries and not accessible by me. In other words, I don't have access to the source code that created the objects. So I'll provide C++ source code for a COM object and class I named 'CA' for 'Class A'. I'll also provide the binary for anyone who wants to register it and run any of my demo code here. However, I'll show the outputs along with the various programs so that those who do not wish to delve into this very deep will still be able to follow along with my discussion if interested. For folks that may be really 'into' this sort of thing, I'll provide directions also for compiling the C++ source into a dll. I personally have Microsoft Visual C++ 6, Microsoft Visual C++ 2008 (VC9), Dev-C++, and CodeBlocks. Unfortunately, the GNU compilers don't seem to like Microsoft's Component Object Model very much. I fought long and hard with Dev-C++ to get clean compiles and working dlls but with mixed results. Sometimes on some computers it can be made to work, and on others it won't. Pretty much disgusted with the whole thing I only tried CodeBlocks a few times without much success. So if anyone wants to fight with those development systems I'll try to help, but can't promise success. The Microsoft compilers work fine though, and Visual C++ 2008 Express can be downloaded for free.
Now I realize this is a PowerBASIC oriented site so I hope I'm not offending anyone – especially including Jose – by the quantity of C++ code I'm going to post. However, I wouldn't do this if I didn't feel that there were over-riding benefits to it for PowerBASIC programmers who may, like me, have been confused by the almost mystical nature of COM objects and Apis. For I feel that to really understand this stuff one must be willing to at least look closely at Microsoft's C & C++ oriented documentation concerning the technology, not to mention in addition that all the books on COM are written for C++ programmers. But PowerBASIC programmers do not dismay! There will be lots of PowerBASIC code too. In fact, my greatest personal satisfaction in developing this code I'm going to post occurred when I finally got everything working in PowerBASIC – for it was only then that I felt that I fully understood what was going on.
So to begin with I'll post an only slightly modified version of the program I mentioned above regarding my ObjPtr() demo in the PowerBASIC forum – the only modification really being some more comments explaining what is going on in the code. It will essentially be this program that I will be re-building in C++ as a COM dll and accessing with a PowerBASIC client in just a bit....
'ObjPtr.bas
#Compile Exe 'While the interface declarations just
#Dim All 'below for I_X and I_Y can be deleted
Declare Sub pFn(dwPtr As Dword) 'and this program will still work, I...
Interface I_X : Inherit IUnknown '...thought for clarity sake it might be
Method Fx1() 'worthwhile to include them for the purpose
Method Fx2() 'of making the point that COM architecture
End Interface 'is based on the idea of seperating the...
Interface I_Y : Inherit IUnknown '...interface from the implementation of
Method Fy1() 'the interface. Here, I_X and I_Y are
Method Fy2() 'declared - but not implemented. They are
End Interface 'implemented in class CA, and the ObjPtr()
Class CA
Interface I_X : Inherit IUnknown '...function can be used to obtain the
Method Fx1() 'address of each interface's VTbl. Where
Print "Called Fx1()" 'PowerBASIC seems to differ somewhat from
End Method 'C++ is that in this situation in C++ the
'Sizeof(CA) would be 8 and those 8 bytes
Method Fx2() 'would be two contiguous VTable pointers.
Print "Called Fx2()" 'PowerBASIC will return two interface
End Method 'pointers also but they do not appear to
End Interface 'be contiguous. Below the I_X pVTbl is
'1280208 and the I_Y interface pointer is
Interface I_Y : Inherit IUnknown 'at 1280340 - not 1280212. Nonetheless
Method Fy1() 'they each can be used to obtain the
Print "Called Fy1()" 'address of each interface's VTable, and
End Method 'the function pointers held in the VTables
'can be used to call the interface
Method Fy2() 'functions. This probably isn't really
Print "Called Fy2()" 'recommended but this exercise at least
End Method 'shows the memory layout.
End Interface
End Class
Function PBMain() 'When one uses ObjPtr() on a variable of type
Local pVTbl As Dword Ptr 'interface one obtains a pointer to the base
Local VTbl As Dword Ptr 'address of the interface/vtable. In the case
Register i As Long 'here where each interface only has two functions
Local ifX As I_X 'the base address will point to a block of memory
Local ifY As I_Y 'occupying 20 bytes - 12 bytes for pointers to
'the three IUnknown functions of QueryInterface(),
Let ifX = Class "CA" 'AddRef(), and Release(), and 8 more bytes for
Let ifY = Class "CA" 'pointers to the two functions. In the output
Call ifX.Fx1() : Call ifX.Fx2() 'below pVTbl=1280208 represents a memory address
Call ifY.Fy1() : Call ifY.Fy2() 'where the number 4200192 is stored - and 4200192
'is the base address of the I_X Virtual Function
'Call methods using 'Table. If one sets another Dword Ptr variable
'interface/vtable 'such as VTbl to this base address in the VTable,
'pointers. 1st I_X 'one will be able to step through the VTable in
pVTbl=ObjPtr(ifX) 'four byte increments using base pointer
Print "pVTbl = " pVTbl 'subscript notation, i.e., @VTbl[i], and output
VTbl=@pVTbl[0] 'either the pointer address in the VTable -
Print "VTbl = " VTbl 'Varptr(@VTbl[i]), or the function/method address
Print:Print 'being pointed to - @VTbl[i].
Print " i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i]"
Print "========================================================================="
For i=0 To 4
If i<=2 Then
Print i, Varptr(@VTbl[i]), @VTbl[i]; " IUnknown Fns (better not call!)"
Else 'The reason we had better not try to
Print i, Varptr(@VTbl[i]), @VTbl[i],; 'call the IUnknown procedures in the same
Call DWord @VTbl[i] Using pFn(0) 'manner as we are calling the I_X and I_Y
End If 'methods that we have implemented is that
Next i 'we are using function pointers to call
Print:Print 'these functions, and to do that one needs
'a properly configured function pointer
'Then I_Y 'definition. At top our pFn Declare won't
pVTbl=ObjPtr(ifY) 'work for the IUnknown functions as their
Print "pVTbl = " pVTbl 'function signatures are quite different.
VTbl=@pVTbl[0] 'If you try to call them a crash will be
Print "VTbl = " VTbl 'quite imminent in your future.
Print
Print " i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i]"
Print "========================================================================="
For i=0 To 4
If i<=2 Then
Print i, Varptr(@VTbl[i]), @VTbl[i]; " IUnknown Fns (better not call!)"
Else 'One other issue observant readers may note
Print i, Varptr(@VTbl[i]), @VTbl[i],; 'is that the pFn Declare shows a DWord
Call DWord @VTbl[i] Using pFn(0) 'parameter in the declaration of the function
End If 'to be used in calling the interface functions,
Next i 'but the interface functions lack any function
Waitkey$ 'parameters. This is due to the fact that all
'OOP implementations pass a hidden class pointer
PBMain=0 'as the first argument of all implemented
End Function 'function calls. If I didn't use this dummy
'argument the program would likely crash after
'the function call when a non-existent parameter
'Called Fx1() 'would be popped off the stack.
'Called Fx2()
'Called Fy1()
'Called Fy2()
'
'pVTbl = 1280208
'VTbl = 4200192
'
' i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i]
'=========================================================================
' 0 4200192 4208481 IUnknown Fns (better not call!)
' 1 4200196 4208461 IUnknown Fns (better not call!)
' 2 4200200 4208604 IUnknown Fns (better not call!)
' 3 4200204 4198799 Called Fx1()
' 4 4200208 4198867 Called Fx2()
'
'pVTbl = 1280340
'VTbl = 4200152
'
' i Varptr(@VTbl[i]) @VTbl[i] Call Dword @VTbl[i]
'=========================================================================
' 0 4200152 4208481 IUnknown Fns (better not call!)
' 1 4200156 4208461 IUnknown Fns (better not call!)
' 2 4200160 4208604 IUnknown Fns (better not call!)
' 3 4200164 4198935 Called Fy1()
' 4 4200168 4199003 Called Fy2()
I believe there may be some useful material in the above simple program, and I believe fully understanding it may be a prerequisite to understanding what follows. What follows is an attempt to create an actual component in C++ containing the I_X and I_Y interfaces which we'll take apart piece by piece in our client PowerBASIC and C++ programs. And in our client programs we'll be able to call the IUnknown functions harmlessly and generate output statements from within them – unlike in the above program. The only slight modification to the I_X and I_Y interfaces from that above is that I added an integer parameter to the Fx() / Fy() functions – which parameter value just gets printed out to the console along with the message that the function was called. Below brace yourself for not one, not two, not even three, but for no less than seven C++ source files that comprise the code behind this simple COM object (some are real short). The seven files are as follows with brief descriptions of what is in each one. I will attach all this stuff in a pkzip file for you....
Server.cpp This file comprises the component's dll housing and contains the only exported functions (4) and DllMain().
CA.cpp This file consists of the implementations of component CA, its Class Factory, and the two interfaces.
Registry.cpp This file contains boilerplate registry code for registering and unregistering the component
CA.h Declarations of class CA and class CAClassFactory
Ifunctions.h Guids and declarations of abstract base classes I_X and I_Y that are implemented in CA.cpp
Registry.h Declarations of RegisterServer() and UnregisterServer() so they can be referenced in Server.cpp
CA.def Module Definition File listing the four exported functions so the linker can export them.
Now for the files...
Server.cpp
//Server.cpp //Note that all the functions in this file are exported. That's what
#include <windows.h> //STDAPI in C mumbo jumbo stands for. Its a macro that expands to
#include <initguid.h> //extern "C" HRESULT __export __stdcall. The extern "C" part tells the
#include "CA.h" //C++ compiler not to mangle the function names so that other system
#include "Registry.h" //code can recognize these names in a GetProcAddress() call.
//Globals
HINSTANCE g_hModule = NULL; //Store dll instance handle
const char g_szFriendlyName[] = "Com Object CA"; //Store friendly name of component
const char g_szVerIndProgID[] = "ComObject.CA"; //Store Version Independent ProgID
const char g_szProgID[] = "ComObject.CA.1"; //Store Versioned Program ID.
long g_lObjs = 0; //count of outstanding objects
long g_lLocks = 0; //used to keep server from unloading
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv)
{
CAClassFactory* pCF=NULL; //DllGetClassObject() is critically important because
HRESULT hr; //it is this function that the operating system calls
//when a client attempts to instantiate an object
if(rclsid!=CLSID_CA) //contained within this dll through a CoCreateInstance()
return E_FAIL; //call, or, in the case of the PowerBASIC compiler when
pCF=new CAClassFactory; //it internally makes the call on the client's behalf.
if(pCF==0) //Whether PowerBASIC does it or we do it through a
return E_OUTOFMEMORY; //CoCreateInstance() call, the operating system locates
hr=pCF->QueryInterface(riid,ppv); //the path to the dll containing some specific CLSID
if(FAILED(hr)) //from the \CLSID\InProcServer32 key in the registry,
delete pCF; //does a LoadLibrary() and GetProcAddress() call on the
//dll, and then calls DllGetClassObject(). If the CLSID
return hr; //passed in matches CLSID_CA, then the C++ 'new' operator
} //is used to create an instance of CA's Class Factory (in...
STDAPI DllCanUnloadNow() //CA.cpp. Having done that it then checks to see if it
{ //can obtain an IUnknown or IClassFactory pointer on CA's
if(g_lObjs||g_lLocks) //Class Factory. If it can the reference count on the
return S_FALSE; //class factory is incremented. What happens next is
else //critical. COM internal system code now calls
return S_OK; //CAClassFactory::CreateInstance() and that is what
} //actually causes a new instance of the CA class to be...
STDAPI DllRegisterServer() //created - which creation causes memory allocations for
{ //both the class's VTable pointers and the VTables
return RegisterServer //themselves. Further, class CA implements the abstract
( //base classes I_X and I_Y, and pointers to the
g_hModule, //implementations of the pure virtual functions contained
CLSID_CA, //within these base classes are placed in the respective
g_szFriendlyName, //VTable of each interface. At this point we have an
g_szVerIndProgID, //object in memory which can be called to do work for us.
g_szProgID
); //DllRegisterServer() and DllUnregisterServer are for
} //adding or eliminating class ids, program ids, etc.,...
STDAPI DllUnregisterServer() //relating to the component from the registry. They are
{ //called by the RegSvr32.exe Windows component. For
return UnregisterServer //example, on this development machine CA.dll is in
( //C:\Code\VStudio\VC++6\Projects\COM\ComObjects\CA\Release\CA.dll
CLSID_CA, //I'd register it by doing this...
g_szVerIndProgID,
g_szProgID //RegSvr32 C:\Code\VStudio\VC++6\Projects\COM\ComObjects\CA\Release\CA.dll
);
} //Having done that I thankfully saw a message box telling...
BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved)
{
switch (reason) //me registration was successful. Registry code is pretty
{ //horrible and in this code was relegated to Registry.cpp
case DLL_PROCESS_ATTACH: //Note that that code needs the dll's instance handle
g_hModule=hInst; //and that is saved just at left in DllMain in a global
break; //variable just for that purpose.
case DLL_PROCESS_DETACH:
break;
}
return TRUE; //Returning FALSE causes the dll to not load.
}
//End Server.cpp
CA.cpp
//CA.cpp //If you don't mind doing command line compiling with
#include <windows.h> //Microsoft C++ 6 you can use this command line...
#include <stdio.h> //
#include "CA.h" //cl /LD CA.cpp Server.cpp Registry.cpp UUID.lib Advapi32.lib Ole32.lib CA.def
CA::CA() //CA Constructor. The creation/instantiation of every
{ //object will cause a thread safe increment of the global
m_lRef=0; //count of CA objects created in the Server. The CA
InterlockedIncrement(&g_lObjs); //member variable m_lRef will count the number of outstanding
} //interface pointers handed out through QueryInterface().
CA::~CA()
{ //When a CA object is destroyed the count of objects is
InterlockedDecrement(&g_lObjs); //decremented by the thread safe InterlockedDecrement()
} //function.
HRESULT __stdcall CA::QueryInterface(REFIID riid, void** ppv)
{
*ppv=0; //Store initial null at address pointed to by ppv.
if(riid==IID_IUnknown) //If GUID of either IID_IUnknown or IID_IX
*ppv=(I_X*)this; //is passed 'in', cast this pointer to point
else if(riid==IID_I_X) //to the 1st of the two pVTbl pointers that constitute
*ppv=(I_X*)this; //the class. If client wants an IID_IY interface, return
else if(riid==IID_I_Y) //the 2nd pointer - which pointer points to the IY
*ppv=(I_Y*)this; //VTable. If *ppv has been set, then do an AddRef() and
if(*ppv) //return S_OK. Otherwise, return E_NOINTERFACE. The
{ //printf statement below will only execute if a non-
AddRef(); //supported interface IID was passed in through the
return S_OK; //REFIID parameter. This is how the Called CA::QueryInterface()
} //output was generated in the client app through a function
printf("Called CA::QueryInterface()\n"); //pointer call.
return(E_NOINTERFACE);
}
ULONG __stdcall CA::AddRef() //The member variable of class CA this->m_lRef
{ //counts interface pointers handed out by
printf("Called CA::AddRef()\n"); //QueryInterface(). InterlockedIncrement takes
return InterlockedIncrement(&m_lRef); //a pointer parameter so the address ( & ) of
} //this->m_lRef is passed in.
ULONG __stdcall CA::Release() //When you are done using an interface pointer
{ //Release() should be called on the pointer so
printf("Called CA::Release()\n"); //that the count of references to the object can
if(InterlockedDecrement(&m_lRef)==0) //be decremented. When the count reaches zero
{ //the class automatically deletes itself from
delete this; //memory., i.e., delete this;. However, the
return 0; //dll server may still reside in memory if the
} //global count of objects created is greater
//than zero, i.e., g_lObjs isn't zero..
return m_lRef;
}
HRESULT __stdcall CA::Fx1(int iNum) //These functions just output a
{ //message that they were called
printf("Called Fx1() : iNum = %u\n",iNum); //plus the value of the parameter
return S_OK; //passed in. Fx1() and Fx2() are
} //part of the I_X interface/vtable
HRESULT __stdcall CA::Fx2(int iNum) //and Fy1() and Fy2() part of the
{ //I_Y vtable, i.e., these respective
printf("Called Fx2() : iNum = %u\n",iNum); //vtables contain pointers to these
return S_OK; //functions.
}
HRESULT __stdcall CA::Fy1(int iNum)
{
printf("Called Fy1() : iNum = %u\n",iNum);
return S_OK;
}
HRESULT __stdcall CA::Fy2(int iNum)
{
printf("Called Fy2() : iNum = %u\n",iNum);
return S_OK;
}
CAClassFactory::CAClassFactory() //CAClassFactory Constructor. Four procedures are exported from
{ //a com server. They are DllGetClassObject(), DllCanUnloadNow()
m_lRef=0; //DllRegisterServer(), and DllUnregisterServer(). When a
} //client app calls CoCreateInstance() with the CLSID of a....
CAClassFactory::~CAClassFactory() //component that can be located in the registry, code internal
{ //to COM's implementation will do a LoadLibrary() and GetProcAddress()
//MathClassFactory Destructor //on DllGetProcAddress(). See my discussion in Server.cpp about this.
}
HRESULT __stdcall CAClassFactory::QueryInterface(REFIID riid, void** ppv)
{
*ppv=0;
if(riid==IID_IUnknown || riid==IID_IClassFactory) //In this component that doesn't do very much,
*ppv=this; //the whole class factory thing seems kind of
if(*ppv) //superfluous. In other words, when COM Services
{ //calls DllGetClassObject(), why not just create
AddRef(); //the component CA directly with the C++ new
return S_OK; //operator instead of 1st creating a class
} //factory which in this case does absolutely
//nothing but create and destroy itself (and
return E_NOINTERFACE; //uses new to create CA in the process)? Well,
} //I suppose you can think of the class factory...
ULONG __stdcall CAClassFactory::AddRef() //concept something like the WM_CREATE message
{ //in Windows Api coding. In that context one
return InterlockedIncrement(&m_lRef); //usually creates the various user interface
} //elements, i.e., buttons, labels, etc., that...
ULONG __stdcall CAClassFactory::Release() //will appear on the window being created. That
{ //is how the class factory can be used. It does
if(InterlockedDecrement(&m_lRef)==0) //whatever it takes to uniquely create the object.
{
delete this;
return 0;
}
return m_lRef;
}
HRESULT __stdcall CAClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void** ppvObj)
{
HRESULT hr; //You won't find anywhere within the code here within any
CA* pCA; //of these project files where CAClassFactory::CreateInstance()
//is called to create a new instance of class CA. This
*ppvObj=0; //function gets called in a somewhat roundabout manner by
pCA=new CA; //the client app's CoCreateInstance() or CoGetClassObject()
if(pCA==0) //call. See my discussion in Server.cpp. First the Windows
return E_OUTOFMEMORY; //COM subsystem loads the dll from info it obtains on the
hr=pCA->QueryInterface(riid,ppvObj); //class from the registry. Then it calls DllGetClassObject(),
if(FAILED(hr)) //which is an exported function. Internal Windows code then
delete pCA; //creates a CAClassFactory instance and from that instance
//calls the CAClassFactory::CreateInstance() code you see
return hr; //here. And just left you see where a 'new' CA is created,
} //and a QueryInterface() done on it to see if riid is there.
HRESULT __stdcall CAClassFactory::LockServer(BOOL fLock)
{
if(fLock) //This function can be called to lock the dll in memory
InterlockedIncrement(&g_lLocks); //even if no outstanding instances of CA are present. This
else //works good in cases where you need to create and destroy
InterlockedDecrement(&g_lLocks); //objects without having to reload the dll between creation/
//destruction cycles.
return S_OK;
}
//End CA.cpp
Ifunctions.h
//IFunctions.h
DEFINE_GUID(CLSID_CA,0x20000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04);
DEFINE_GUID(IID_I_X,0x20000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05);
DEFINE_GUID(IID_I_Y,0x20000000,0x0000,0x0000,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06);
interface I_X : IUnknown //In C++ lingo an interface declaration such as this is known
{ //as an abstract base class. It can't be instantiated directly
virtual HRESULT __stdcall Fx1(int)=0; //because it only contains what are known as pure virtual
virtual HRESULT __stdcall Fx2(int)=0; //functions (note the '=0' part). What is actually going on
}; //here is the creation of a specific footprint in memory that...
interface I_Y : IUnknown //COM uses as its fundamental basis of operation. Let me
{ //elaborate. The C++ language was created long before COM and
virtual HRESULT __stdcall Fy1(int)=0; //most implementations of the language allocate a pointer
virtual HRESULT __stdcall Fy2(int)=0; //within a class for each abstract base class from which it
}; //inherits. If you look just below and left you'll see that class
//End Ifunctions.h //CA inherits publiclly from interfaces I_X and I_Y. In C++...
//CA.h //the term interface means the same thing as a struct, which
#include "IFunctions.h" //in PowerBASIC means the same thing as a good old BASIC TYPE.
extern long g_lObjs; //This #define macro can be found in objbase.h. Further, in
extern long g_lLocks; //C++ structs are almost the same thing as classes except that
class CA : public I_X, public I_Y //all their members are public by default, i.e., visible in
{ //inheriting classes. That is the simple reason they are used.
public: //Now, when class CA is constructed by most C++ compilers a memory
CA(); //Constructor //allocation will be made for two pointers that point to other
virtual ~CA(); //Destructor //blocks of memory where pointers to the implemented functions
//Iunknown Functions //will be stored. This is the virtual function table. Our
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv); //client programs will show its
virtual ULONG __stdcall AddRef(); //exact structure in withering detail. Suffice it to say here
virtual ULONG __stdcall Release(); //though that the block of memory CA allocates for interface I_X
//I_X Interface Methods/Functions //(its virtual function table) will be 20 bytes, and the same
virtual HRESULT __stdcall Fx1(int); //for interface I_Y. This is because these interfaces, each of
virtual HRESULT __stdcall Fx2(int); //which only contains two functions, themselves inherit from
//I_ Interface Methods/Functions //another struct, i.e., IUnknown, which itself contains pointers
virtual HRESULT __stdcall Fy1(int); //to three functions – QueryInterface(), AddRef(), and Release().
virtual HRESULT __stdcall Fy2(int); //This is the absolute most fundamental rule in the COM standard
protected: //that all interfaces, i.e., structs, classes, virtual function
long m_lRef; //tables, whatever you want to call them – must have pointers to
}; //QueryInterface(), AddRef(), and Release() as their first three...
class CAClassFactory : public IClassFactory //members. You can easily see that
{ //to be true by just looking directly
public: //left at the two classes there that
CAClassFactory(); //contain these functions. Note that
virtual ~CAClassFactory(); //it may not look like these functions
public: //are at position one because they are
//IUnknown //not first in the declarations, but that
virtual HRESULT __stdcall QueryInterface(REFIID, void**); //is somewhat confusing because the order
virtual ULONG __stdcall AddRef(); //of declaration there isn't the
virtual ULONG __stdcall Release(); //controlling factor, but rather the
//IclassFactory //controlling factor is the inheritance
virtual HRESULT __stdcall CreateInstance(LPUNKNOWN, REFIID, void**); //chain which forms the basis of the
virtual HRESULT __stdcall LockServer(BOOL); //class. At left CAClassFactory inherits
protected: //from IclassFactory which is a system
//Reference Count //defined class. And in the declaration
long m_lRef; //of IclassFactory it would be found that
}; //it itself inherits from IUnknown.
//End CA.h
Registry.h
HRESULT RegisterServer(HMODULE hModule, const CLSID& clsid, const char* szFriendlyName, const char* szVerIndProgID, const char* szProgID);
HRESULT UnregisterServer(const CLSID& clsid, const char* szVerIndProgID, const char* szProgID);
Registry.cpp
// Registry.cpp
#include <objbase.h>
const int CLSID_STRING_SIZE = 39;
BOOL setKeyAndValue(const char* szKey, const char* szSubkey, const char* szValue)
{
char szKeyBuf[1024];
long lResult;
HKEY hKey;
strcpy(szKeyBuf,szKey); //Copy keyname into buffer.
if(szSubkey!=NULL) // Add subkey name to buffer.
{
strcat(szKeyBuf, "\\") ;
strcat(szKeyBuf, szSubkey ) ;
}
//Create and open key and subkey.
lResult=RegCreateKeyEx(HKEY_CLASSES_ROOT,szKeyBuf,0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,&hKey,NULL);
if(lResult!=ERROR_SUCCESS)
return FALSE ;
if(szValue!=NULL) //Set the Value.
RegSetValueEx(hKey,NULL,0,REG_SZ,(BYTE*)szValue,strlen(szValue)+1);
RegCloseKey(hKey);
return TRUE ;
}
void CLSIDtochar(const CLSID& clsid, char* szCLSID, int length) // Convert a CLSID to a char string.
{
LPOLESTR wszCLSID=NULL;
HRESULT hr;
hr=StringFromCLSID(clsid,&wszCLSID); // Get CLSID
if(SUCCEEDED(hr))
{
wcstombs(szCLSID, wszCLSID,length); // Covert from wide characters to non-wide.
CoTaskMemFree(wszCLSID); // Free memory.
}
}
LONG recursiveDeleteKey(HKEY hKeyParent, const char* lpszKeyChild) // Key to delete
{
char szBuffer[256];
DWORD dwSize=256 ;
HKEY hKeyChild;
FILETIME time;
LONG lRes;
lRes=RegOpenKeyEx(hKeyParent,lpszKeyChild,0,KEY_ALL_ACCESS,&hKeyChild); //Open the child.
if(lRes!=ERROR_SUCCESS)
return lRes;
while(RegEnumKeyEx(hKeyChild,0,szBuffer,&dwSize,NULL,NULL,NULL,&time)==S_OK) //Enumerate all of the decendents of this child.
{
lRes=recursiveDeleteKey(hKeyChild,szBuffer); //Delete the decendents of this child.
if(lRes!=ERROR_SUCCESS)
{
RegCloseKey(hKeyChild); //Cleanup before exiting.
return lRes;
}
dwSize=256;
}
RegCloseKey(hKeyChild); // Close the child.
return RegDeleteKey(hKeyParent,lpszKeyChild); //Delete this child.
}
HRESULT RegisterServer(HMODULE hModule,const CLSID& clsid,const char* szFriendlyName,const char* szVerIndProgID,const char* szProgID)
{
char szCLSID[CLSID_STRING_SIZE];
char szModule[512];
char szKey[64];
if(GetModuleFileName(hModule,szModule,sizeof(szModule)/sizeof(char)))
{
CLSIDtochar(clsid, szCLSID,sizeof(szCLSID)); //Get server location &Convert the CLSID into a char.
strcpy(szKey, "CLSID\\"); //Build the key CLSID\\{...}
strcat(szKey,szCLSID);
setKeyAndValue(szKey,NULL,szFriendlyName); //Add the CLSID to the registry.
setKeyAndValue(szKey, "InprocServer32", szModule); //Add the server filename subkey under the CLSID key.
setKeyAndValue(szKey, "ProgID", szProgID); //Add the ProgID subkey under the CLSID key.
setKeyAndValue(szKey,"VersionIndependentProgID",szVerIndProgID); //Add the version-independent ProgID subkey under CLSID key.
setKeyAndValue(szVerIndProgID, NULL, szFriendlyName); //Add the version-independent ProgID subkey under HKEY_CLASSES_ROOT.
setKeyAndValue(szVerIndProgID, "CLSID", szCLSID);
setKeyAndValue(szVerIndProgID, "CurVer", szProgID);
setKeyAndValue(szProgID, NULL, szFriendlyName) ; //Add the versioned ProgID subkey under HKEY_CLASSES_ROOT.
setKeyAndValue(szProgID, "CLSID", szCLSID) ;
}
else
return E_FAIL;
return S_OK ;
}
HRESULT UnregisterServer(const CLSID& clsid, const char* szVerIndProgID, const char* szProgID)
{
char szCLSID[CLSID_STRING_SIZE];
char szKey[64];
LONG lResult;
CLSIDtochar(clsid, szCLSID, sizeof(szCLSID)); //Convert the CLSID into a char.
strcpy(szKey, "CLSID\\"); //Build the key CLSID\\{...}
strcat(szKey, szCLSID) ;
lResult=recursiveDeleteKey(HKEY_CLASSES_ROOT, szKey); //Delete the CLSID Key - CLSID\{...}
lResult=recursiveDeleteKey(HKEY_CLASSES_ROOT, szVerIndProgID); //Delete the version-independent ProgID Key.
lResult=recursiveDeleteKey(HKEY_CLASSES_ROOT, szProgID) ; //Delete the ProgID key.
return S_OK ;
}
//End Registry.cpp
CA.def -- I haven't been able to figure out how to successfully get functions exported from COM objects without these darn
// .def files. I think it might be possible, but nothing I've tried works, including but not limited to various
// combinations of __declspec(dllexport), extern "C", etc., etc., etc.
;//CA.def
LIBRARY "CA"
DESCRIPTION "CA Windows Dynamic Link Library"
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
....continued
Well, pretty intimidating I guess. I'll try to explain it – particularly how the class is instantiated through COM services and DllGetClassObject(). Note that the first file I listed was Server.cpp and DllGetClassObject() is the first function in that file.
If you have Visual C++ 6 and your environment is set up for command line compiling you can easily compile it into a dll with the following command line...
cl /LD CA.cpp Server.cpp Registry.cpp UUID.lib Advapi32.lib Ole32.lib CA.def
Alternately, you can create a blank dll project in the IDE, add the files, and compile & link through the IDE. There is even an option within the IDE to register the dll. Or, you can just use regsvr32 on the attached CA.dll I'll attach to this post.
To create the dll with the huge, slow, lumbering, ponderous bulk of VC 9 (Visual C++ 2008), you'll need to fight your way through a nearly impenetrable tangle of project property sheets until you get to a place where you can set the default character set to be used to be something other than UNICODE (multy-byte or NOT SET), and you'll have to let the linker know about the module definition file containing the exports. Here are the steps:
1) Create Dll Project In VStudio C++ 2008;
2) Add all CA files discussed here to project;
3) Open Properties Sheet for project CA and under
CA Property Pages \Configuration Properties \General \Project Defaults \Character Set...
Choose Not Set or Multi-Byte Character Set;
4) Under \Configuration Properties \Linker \Input \Module Definition File...
Set the Module Definition File to CA.def;
5) Use my RegistryAlt.cpp file instead of Registry.cpp. Rename it to Registry.cpp.
Finally, although it will compile & link without doing this, you'll get piles of security warnings due to the use of strcpy(), strcat(), and wcstombs() in Registry.cpp. I'll attach an alternate Registry.cpp (RegistryAlt.cpp) in the attachments that will compile clean.
If you want to attempt to create the dll with the GNU compiler collection, contact me directly & I'll try to help. There are some issues.
Following is a whirlwind tour of how the class CA gets created so that a client can call the interface functions. First, the dll needs to be registered with regsvr32 (or you can do it manually or in code yourself but I doubt you would want to do that). What the registration process does is store the program ids (ComObject.CA.1) under HKEY_CLASSES_ROOT and the path to the dll file in HKEY_CLASSES_ROOT\CLSID\{20000000-0000-0000-0000-000000000004}\InProcServer32. The registered path to CA.dll on the machine on which I'm writing this is...
C:\Code\Vstudio\VC++6\Projects\COM\ComObjects\CA\Release\CA.dll
In our PowerBASIC program I'll soon make the following function call...
Local pVTbl As Dword Ptr
Call CoCreateInstance($CLSID_CA, Byval %NULL, %CLSCTX_INPROC_SERVER, $IID_IUnknown, pVTbl)
The chain of events that will ensue is as follows. The operating system will search the registry for a registered in process component ( a dll )with a class id of $CLSID_CA. This is the 16 byte number listed just above. When it locates this object in the registry it will have the path to the dll listed also above. With that information it can do a LoadLibrary() on the Dll and a GetProcAddress() on the exported function DllGetClassObject() from Server.cpp. At this point you need to take a close look at DllGetClassObject() in my provided code (and also remember where we are at right now, and that is inside a system call, i.e., CoCreateInstance(). Inside DllGetClassObject() a CAClassFactory pointer (CAClassFactory* pCF) is allocated. This can be done because we are inside DllGetClassObject and DllGetClassObject is inside the Dll where the CAClassFactory class is both defined and implemented. Having obtained this pointer the C++ 'new' operator is used to create a new instance in memory of the CAClassFactory class. The 'new' operator in C++ supplanted malloc from C, and is its new memory allocation mechanism. Having created the class, QueryInterface() on the class factory is called and the riid parameter will be the IID of the class factory, and a pointer returned as the last parameter of a successful call will be a pointer to the class factory, i.e., ppv.
hr=pCF->QueryInterface(riid,ppv);
One of the two interface members (beyond the three of IUnknown) of IClassFactory is CreateInstance() and within CAClassFactory::CreateInstance() is another use of the C++ new operator but this time to create a new instance of class CA – our container class of our desired I_X and I_Y interfaces. This part is confusing and after I have presented several programs and discussed COM memory & low level access techniques in some detail I will return to the subject to try to improve your understanding of it. For now, lets just take a look at several programs which access the class CA and its interfaces. First the PowerBASIC Console Compiler 5.0 program, then the same exact thing in C++...
#Compile Exe "CAClient.exe"
#Dim All
#Include "Win32Api.inc"
%CLSCTX_INPROC_SERVER =&H1???
$IID_IUnknown =Guid$("{00000000-0000-0000-C000-000000000046}") 'Microsoft Defined
$CLSID_CA =Guid$("{20000000-0000-0000-0000-000000000004}") 'Class ID of Class CA, ie., Class A
$IID_IX =Guid$("{20000000-0000-0000-0000-000000000005}") 'Interface X
$IID_IY =Guid$("{20000000-0000-0000-0000-000000000006}") 'Interface Y
$IID_Junk =Guid$("{12345678-9876-5432-1012-345678901234}") 'Junk Number So QueryInterface() Fails
Declare Function ptrQueryInterface(Byval this As Dword, Byref iid As Guid, Byref pUnknown As Any) As Long
Declare Function ptrAddRef(Byval this As Dword) As Dword
Declare Function ptrRelease(Byval this As Dword) As Dword
Declare Function ptrFn(Byval this As Dword, ByVal iNum As Long) As Long
Interface I_X $IID_IX : Inherit IUnknown
Method Fx1(ByVal iNum As Long) As Long
Method Fx2(ByVal iNum As Long) As Long
End Interface
Interface I_Y $IID_IY : Inherit IUnknown
Method Fy1(ByVal iNum As Long) As Long
Method Fy2(ByVal iNum As Long) As Long
End Interface
Function PBMain() As Long
Local pVTbl,VTbl,pUnk As Dword Ptr
Local hResult As Long
Register i As Long
hResult=CoCreateInstance($CLSID_CA, Byval %NULL, %CLSCTX_INPROC_SERVER, $IID_IUnknown, pVTbl)
If SUCCEEDED(hResult) Then
Print "CoCreateInstance() For IUnknown On " ProgID$($CLSID_CA) " Succeeded!"
Print "pVTbl = " pVTbl
Print
Print "Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword"
Print "==============================================================================="
For i=0 To 1
VTbl=@pVTbl[i] 'Call...
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[0]) Tab(37)@VTbl[0] " ";
Call DWord @VTbl[0] Using ptrQueryInterface(Varptr(@pVTbl[i]), $IID_Junk, pUnk) To hResult 'QueryInterface()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[1]) Tab(37)@VTbl[1] " ";
Call DWord @VTbl[1] Using ptrAddRef(Varptr(@pVTbl[i])) To hResult 'AddRef()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[2]) Tab(37)@VTbl[2] " ";
Call DWord @VTbl[2] Using ptrRelease(Varptr(@pVTbl[i])) To hResult 'Release()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[3]) Tab(37)@VTbl[3] " ";
Call DWord @VTbl[3] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fx1() / Fy1()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[4]) Tab(37)@VTbl[4] " ";
Call DWord @VTbl[4] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fy1() / Fy2()
Print
Next i
Call DWord @VTbl[2] Using ptrRelease(pVTbl) To hResult
End If
Waitkey$
PBMain=0
End Function
'Called CA::AddRef()
'Called CA::AddRef()
'Called CA::Release()
'Called CA::AddRef()
'Called CA::Release()
'CoCreateInstance() For IUnknown On ComObject.CA.1 Succeeded!
'pVTbl = 9569824
'
'Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword
'===============================================================================
'9569824 268464412 268439632 Called CA::QueryInterface()
'9569824 268464416 268439776 Called CA::AddRef()
'9569824 268464420 268439808 Called CA::Release()
'9569824 268464424 268439888 Called Fx1() : iNum = 0
'9569824 268464428 268439920 Called Fx2() : iNum = 0
'
'9569828 268464392 268440384 Called CA::QueryInterface()
'9569828 268464396 268440400 Called CA::AddRef()
'9569828 268464400 268440416 Called CA::Release()
'9569828 268464404 268439952 Called Fy1() : iNum = 1
'9569828 268464408 268439984 Called Fy2() : iNum = 1
'
'Called CA::Release()
This particular version of the program must use PowerBASIC's includes because Jose's includes declare CoCreateInstance() somewhat differently. Here is a version that will work with Jose's includes...
#Compile Exe "CAClient.exe"
#Dim All
#Include "Windows.inc"
#Include "ObjBase.inc"
%CLSCTX_INPROC_SERVER =&H1???
$IID_IUnknown =Guid$("{00000000-0000-0000-C000-000000000046}") 'Microsoft Defined
$CLSID_CA =Guid$("{20000000-0000-0000-0000-000000000004}") 'Class ID of Class CA, ie., Class A
$IID_IX =Guid$("{20000000-0000-0000-0000-000000000005}") 'Interface X
$IID_IY =Guid$("{20000000-0000-0000-0000-000000000006}") 'Interface Y
$IID_Junk =Guid$("{12345678-9876-5432-1012-345678901234}") 'Junk Number So QueryInterface() Fails
Declare Function ptrQueryInterface(Byval this As Dword, Byref iid As Guid, Byref pUnknown As IUnknown) As Long
Declare Function ptrAddRef(Byval this As Dword) As Dword
Declare Function ptrRelease(Byval this As Dword) As Dword
Declare Function ptrFn(Byval this As Dword, ByVal iNum As Long) As Long
Interface I_X $IID_IX : Inherit IUnknown
Method Fx1(ByVal iNum As Long) As Long
Method Fx2(ByVal iNum As Long) As Long
End Interface
Interface I_Y $IID_IY : Inherit IUnknown
Method Fy1(ByVal iNum As Long) As Long
Method Fy2(ByVal iNum As Long) As Long
End Interface
Function PBMain() As Long
Local pVTbl,VTbl As Dword Ptr
Local pUnk As IUnknown
Local hResult As Long
Register i As Long
hResult=CoCreateInstance($CLSID_CA, Nothing, %CLSCTX_INPROC_SERVER, $IID_IUnknown, pVTbl)
If SUCCEEDED(hResult) Then
Print "CoCreateInstance() For IUnknown On " ProgID$($CLSID_CA) " Succeeded!"
Print "pVTbl = " pVTbl
Print
Print "Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword"
Print "==============================================================================="
For i=0 To 1
VTbl=@pVTbl[i] 'Call...
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[0]) Tab(37)@VTbl[0] " ";
Call DWord @VTbl[0] Using ptrQueryInterface(Varptr(@pVTbl[i]), $IID_Junk, pUnk) To hResult 'QueryInterface()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[1]) Tab(37)@VTbl[1] " ";
Call DWord @VTbl[1] Using ptrAddRef(Varptr(@pVTbl[i])) To hResult 'AddRef()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[2]) Tab(37)@VTbl[2] " ";
Call DWord @VTbl[2] Using ptrRelease(Varptr(@pVTbl[i])) To hResult 'Release()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[3]) Tab(37)@VTbl[3] " ";
Call DWord @VTbl[3] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fx1() / Fy1()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[4]) Tab(37)@VTbl[4] " ";
Call DWord @VTbl[4] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fy1() / Fy2()
Print
Next i
Call DWord @VTbl[2] Using ptrRelease(pVTbl) To hResult
End If
Waitkey$
PBMain=0
End Function
'Called CA::AddRef()
'Called CA::AddRef()
'Called CA::Release()
'Called CA::AddRef()
'Called CA::Release()
'CoCreateInstance() For IUnknown On ComObject.CA.1 Succeeded!
'pVTbl = 9568672
'
'Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword
'===============================================================================
'9568672 268464492 268439920 Called CA::QueryInterface()
'9568672 268464496 268440064 Called CA::AddRef()
'9568672 268464500 268440096 Called CA::Release()
'9568672 268464504 268440160 Called Fx1() : iNum = 0
'9568672 268464508 268440192 Called Fx2() : iNum = 0
'
'9568676 268464472 268440672 Called CA::QueryInterface()
'9568676 268464476 268440688 Called CA::AddRef()
'9568676 268464480 268440704 Called CA::Release()
'9568676 268464484 268440224 Called Fy1() : iNum = 1
'9568676 268464488 268440256 Called Fy2() : iNum = 1
'
'Called CA::Release()
And finally, here is an exact translation of the above into C++...
#include <objbase.h> //CAClient
#include <stdio.h>
const CLSID CLSID_CA ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04}}; //Class ID of Class CA, ie., Class A
const IID IID_I_X ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05}}; //Interface X
const IID IID_I_Y ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06}}; //Interface Y
const IID IID_Junk ={0x12345678,0x9876,0x5432,{0x12,0x34,0x56,0x78,0x98,0x76,0x54,0x32}}; //Junk Number So QueryInterface() Fails
HRESULT (__stdcall* ptrQueryInterface) (int, const IID&, void**); //these are C function pointers somewhat analogous to
ULONG (__stdcall* ptrAddRef) (int); //PowerBASIC's Call Dword setup (but not as easy to
ULONG (__stdcall* ptrRelease) (int); //understand!!!!!).
void (__stdcall* pIFn) (int,int);
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
int main(void)
{
IUnknown* pIUnk=NULL;
unsigned int* pVTbl=0;
unsigned int* VTbl=0;
unsigned int i=0;
HRESULT hr;
hr=CoInitialize(NULL);
if(SUCCEEDED(hr))
{
hr=CoCreateInstance(CLSID_CA,NULL,CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&pVTbl);
if(SUCCEEDED(hr))
{
puts("CoCreateInstance() For IUnknown Succeeded!");
printf("pVTbl = %u\n",pVTbl);
printf("\n");
printf("&pVTbl[i]\t&VTbl[i]\tVTbl[i]\t\tFunction Call Through Pointer\n");
printf("=============================================================================\n");
for(i=0;i<=1;i++)
{
VTbl=(unsigned int*)pVTbl[i]; //Call...
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[0],VTbl[0]);
ptrQueryInterface=(HRESULT(__stdcall*)(int, const IID&, void**)) VTbl[0];
ptrQueryInterface((int)&pVTbl[i],IID_Junk,(void**)&pIUnk); //QueryInterface()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[1],VTbl[1]);
ptrAddRef=(ULONG(__stdcall*)(int)) VTbl[1];
ptrAddRef((int)&pVTbl[i]); //AddRef()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[2],VTbl[2]);
ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
ptrRelease((int)&pVTbl[i]); //Release()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[3],VTbl[3]);
pIFn=(void(__stdcall*)(int,int)) VTbl[3];
pIFn((int)&pVTbl[i],i); //Fx1() / Fy1()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[4],VTbl[4]);
pIFn=(void(__stdcall*)(int,int)) VTbl[4];
pIFn((int)&pVTbl[i],i); //Fx2() / Fy2()
printf("\n");
}
ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
ptrRelease((int)&pVTbl[1]);
}
CoUninitialize();
getchar();
}
return 0;
}
/*
Called CA::AddRef()
Called CA::AddRef()
Called CA::Release()
Called CA::AddRef()
Called CA::Release()
CoCreateInstance() For IUnknown Succeeded!
pVTbl = 10355664
&pVTbl[i] &VTbl[i] VTbl[i] Function Call Through Pointer
=============================================================================
10355664 268464396 268439696 Called CA::QueryInterface()
10355664 268464400 268439840 Called CA::AddRef()
10355664 268464404 268439872 Called CA::Release()
10355664 268464408 268439936 Called Fx1() : iNum = 0
10355664 268464412 268439968 Called Fx2() : iNum = 0
10355668 268464376 268440448 Called CA::QueryInterface()
10355668 268464380 268440464 Called CA::AddRef()
10355668 268464384 268440480 Called CA::Release()
10355668 268464388 268440000 Called Fy1() : iNum = 1
10355668 268464392 268440032 Called Fy2() : iNum = 1
Called CA::Release()
*/
Lets start near the top. Right after the GUIDs and includes are these declare statements...
Declare Function ptrQueryInterface(Byval this As Dword, Byref iid As Guid, Byref pUnknown As Any) As Long
Declare Function ptrAddRef(Byval this As Dword) As Dword
Declare Function ptrRelease(Byval this As Dword) As Dword
Declare Function ptrFn(Byval this As Dword, ByVal iNum As Long) As Long
We are going to be using low level procedural code to access objects created with an OOP language, that is, C++, and in order to do that it will be necessary to call class methods through function pointers. Jose has been doing this sort of thing for years in his work with COM in PowerBASIC, and because of his complete mastery of these techniques and his generosity in providing his code to us, we have been able to access ActiveX controls relatively easily with PowerBASIC. But make no mistake; understanding this function pointer access is fundamental. To understand why we need these declares and function pointers for the type of low level access we are doing lets examine the concept of inheritance from a slightly different angle from that in which it is usually presented, and that angle is the effect of inheritance on the memory layout of inheriting classes.
Say we have a BASIC TYPE like so...
Type SomeType
A As Integer
B As Integer
C As Integer
End Type
And then another type...
Type Another
BasicInterface As SomeType
D As Integer
E As Integer
End Type
What this will look like in memory in terms of Type Another is pretty easy to imagine as we have five 16 bit integers that in total amounts to ten bytes. The first six bytes are A, B, and C of BasicInterface As SomeType, and the last four bytes are D and E of Another. This is a simple example of inheritance and can be found frequently in C language usage. One of the most popular GUI programming toolkits in the Linux world is GTK, and that toolkit is a C language based toolkit where use of this type of inheritance and terminology is common. Now take a look at how our simple class CA might be defined in PowerBASIC, and then in C++ from CA.h...
1st PowerBASIC
Class CA
Interface I_X : Inherit IUnknown '...function can be used to obtain the
Method Fx1() 'address of each interface's VTbl. Where
Print "Called Fx1()" 'PowerBASIC seems to differ somewhat from
End Method 'C++ is that in this situation in C++ the
'Sizeof(CA) would be 8 and those 8 bytes
Method Fx2() 'would be two contiguous VTable pointers.
Print "Called Fx2()" 'PowerBASIC will return two interface
End Method 'pointers also but they do not appear to
End Interface 'be contiguous. Below the I_X pVTbl is
'1280208 and the I_Y interface pointer is
Interface I_Y : Inherit IUnknown 'at 1280340 - not 1280212. Nontheless
Method Fy1() 'they each can be used to obtain the
Print "Called Fy1()" 'address of each interface's VTable, and
End Method 'the function pointers held in the VTables
'can be used to call the interface
Method Fy2() 'functions. This probably isn't really
Print "Called Fy2()" 'recommended but this exercise at least
End Method 'shows the memory layout.
End Interface
End Class
Then C++ from CA.h
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
One of the things you will find in common between the PowerBASIC and C++ declarations is that the interfaces inherit from something named IUnknown, i.e.,
PowerBASIC: Interface I_X : Inherit IUnknown
C++: interface I_X : IUnknown
What is happening here is that these memory structures are having a pre-existing memory structure prepended to themselves just as our TYPE Another had the three A, B, and C integers of SomeType prepended to its structure containing D and E. Actually, IUnknown is either a C struct or a C++ class depending on whether the program is being compiled as a C or C++ program, and for our purposes here in PowerBASIC doing low level COM access is probably best translated as a Type something like this...
Type IUnknown
QueryInterface As Dword Ptr
AddRef As Dword Ptr
Release As Dword Ptr
End Type
....and, since this type is being prepended to both interface I_X and I_Y, then the actual structure of each interface as a virtual function table laid out in memory from some base memory address is as follows for I_X...
base address + 0 pointer to QueryInterface()
base address + 4 pointer to AddRef()
base address + 8 pointer to Release()
base address + 12 pointer to Fx1()
base address + 16 pointer to Fx2()
If one were to have a pointer to this base address such as...
Local pVTbl As Dword Ptr
Local VTbl As Dword Ptr
Local i As Long
And you had code to beg, borrow or steal this pVTbl pointer from somewhere, you could iterate through this Vtable structure in memory like so and call the various functions...
VTbl=@pVTbl 'Get the base address of the virtual function table stored in a Vtable pointer
For i=0 To 4
Call Dword @VTbl[i] Using SomeModelFunctionDefinition To hResult
Next I
The only thing left undefined above is the model function definition to be used with the call Dword statement. A different one is needed for each of the functions to be called, except that Fx1()/Fx2() and Fy1()/Fy2() can use the same one due to their having the same function signature. I just told you all that so that you would understand what the declares are for at the top of the program.
Having flushed some of those details out let's take a look at the stat of the program where we call CoCreateInstance() to get our initial pVTbl on the COM object ComObject.CA.1 whose CLSID is $CLSID_CA:
hResult=CoCreateInstance($CLSID_CA, Byval %NULL, %CLSCTX_INPROC_SERVER, $IID_IUnknown, pVTbl) 'PowerBASIC's Includes
or
hResult=CoCreateInstance($CLSID_CA, pUnk, %CLSCTX_INPROC_SERVER, $IID_IUnknown, pVTbl) 'Jose's Includes
The last output parameter will contain our initial pointer to the virtual function tables of this COM class. But there is that whole issue again! What is a COM class? Well, in anything having to do with computers we're talking computer memory. And when you are talking computer memory the most important questions are "Where is it and how big is it?" That is actually easy to find out. Let us alter the class code for class CA so as to print out the address of the object when it gets instantiated, and its size. Up near the top of CA.cpp is the constructor for class CA as follows...
CA::CA()
{
m_lRef=0;
InterlockedIncrement(&g_lObjs);
}
We'll alter that as follows, recompile, then run the very same PowerBASIC program above...
CA::CA()
{
m_lRef=0;
InterlockedIncrement(&g_lObjs);
printf("sizeof(CA) = %u\n",sizeof(CA));
printf("this = %u\n",this);
}
The above CA class constructor will execute as soon as its class factory creates it in response to a client's CoCreateInstance() call, and the 1st printf call will output the size of the memory allocation for class CA, and the 2nd printf call will use the hidden 'this' pointer to output the base memory location of the allocation. Re-running CAClient now produces this result...
sizeof(CA) = 12
this = 9569824
Called CA::AddRef()
Called CA::AddRef()
Called CA::Release()
Called CA::AddRef()
Called CA::Release()
CoCreateInstance() For IUnknown On ComObject.CA.1 Succeeded!
pVTbl = 9569824
Varptr(@pVTbl) Varptr(@VTbl) @VTbl Function Call With Call Dword
===============================================================================
9569824 268464396 268439696 Called CA::QueryInterface()
9569824 268464400 268439840 Called CA::AddRef()
9569824 268464404 268439872 Called CA::Release()
9569824 268464408 268439936 Called Fx1() : iNum = 0
9569824 268464412 268439968 Called Fx2() : iNum = 0
9569828 268464376 268440448 Called CA::QueryInterface()
9569828 268464380 268440464 Called CA::AddRef()
9569828 268464384 268440480 Called CA::Release()
9569828 268464388 268440000 Called Fy1() : iNum = 1
9569828 268464392 268440032 Called Fy2() : iNum = 1
Called CA::Release()
In studying the output above there certainly is one particular number that kind of 'jumps out' at one due to its repetition in a number of seemingly important places. That number is of course 9569824. It was output both from within the Com object itself and as the value of pVTbl returned from the COM Server in the last parameter of CAClient's CoCreateInstance() call. The other interesting number is '12'.
Lets start with the sizeof(CA)=12. When a C++ class is instantiated which inherits from what are termed 'abstract base classes', the C++ compiler creates/allocates something termed a virtual function table pointer for each abstract base class from which it inherits. If you look at the declaration of class CA in CA.h you'll see this...
class CA : public I_X, public I_Y
{
public:
CA(); //Constructor
virtual ~CA(); //Destructor
//Iunknown Functions
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
//I_X Interface Methods/Functions
virtual HRESULT __stdcall Fx1(int);
virtual HRESULT __stdcall Fx2(int);
//I_ Interface Methods/Functions
virtual HRESULT __stdcall Fy1(int);
virtual HRESULT __stdcall Fy2(int);
protected:
long m_lRef;
};
The line...
class CA : public I_X, public I_Y
...means that class CA is inheriting from two base classes which as it turns out are pure abstract base classes because there is no executable code within these classes. Therefore class CA will have 8 bytes allocated for two pointers; The 1st pointer will point to the I_X virtual function able and the second will point to the I_Y virtual function table. The number 9569824 above is the address where the function table pointer to the I_X Vtable is stored, and that 32 but pointer will occupy bytes 9569824 through bytes 9569827. The I_Y virtual function table pointer will be located in bytes 9569828 through bytes 9569831. That accounts for 8 of the 12 bytes. The last four bytes is for the single long data member contained within the class CA – long m_lRef. The counter data member is there to count outstanding references made on the object. In other words, every time you ask QueryInterface for a pointer to an existing object the reference counter will be incremented, and when a pointer is no longer used the reference counter will be decremented. When it reaches zero the object automatically destroys itself. I've got to say it is reassuring that the same number was returned from the object through the this pointer as we obtained in pVTbl through the return parameter!
Now, stored at those 8 bytes in those two pointers beginning at 9569824 are two very important numbers. If we were to do this...
Print @pVTbl[0]
Print @pVTbl[1]
Our output would be this using the above example's numbers...
268464396
268464376
The top number is where the I_X Vtable starts in memory (268464396), and the bottom number where the I_Y Vtable starts (268464376). Recall we determined above that each of these interfaces/vtables will contain five 32 bit function pointers (three from IUnknown and two for the interface functions). These function pointers will be arranges serially at four byte increments from the base pointer location. You can see this in the 2nd column output under the Varptr(@VTbl) heading. It can be deduced from this that when class CA was instantiated it made memory allocations for not only the 12 bytes previously discussed, but for each 20 byte Vtable. However, it didn't count the Vtable memory in its allocation. That memory would be counted within the interface class. Also, the compiler had to allocate memory for the implemented functions within class CA, but memory allocated for functions isn't counted as part of a class's memory. Pointers to the implemented functions however can be found located within each Vtable, and you can see these numbers in column three of the output under the @VTbl heading. Note that these numbers have irregular spacing due to the fact that depending on the size of the compiled code, their location in memory is somewhat scattered. However, the numbers must be good because when we used function pointers to call the functions through these addresses we obtained perfect results with perfect matching of AddRef and Release counts and no crashes. By examining the code in CA.cpp for these functions you will be able to see how the output was generated.
continued......
Before we started with the client programs I mentioned I'd go into more detail at a later point concerning how class CA is instantiated within the dll as a result of the initial client CoCreateInstance() call. If you are reasonably comfortable with what I have presented so far it may be time to delve into this matter in a bit more detail.
As I may have mentioned, CoCreateInstance() is a wrapper around some more basic COM functionality involving the IClassFactory interface. Let's explore creating the class factory ourselves in the client app and calling CAClassFactory::CreateInstance() directly. Its quite a bit easier in C++ than in PowerBASIC so lets start there so that you can get a feel for what it looks like, then we'll translate that to PowerBASIC. Below is a version of the client series of programs I have been providing that has all error handling removed so as to concisely show the series of steps. Its surprisingly easy.
#include <objbase.h> //CAClient5
#include <stdio.h> //IID_IClassFactory={00000001-0000-0000-C000-000000000046};
const CLSID CLSID_CA ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04}}; //Class ID of Class CA, ie., Class A
const IID IID_I_X ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05}}; //Interface X
const IID IID_I_Y ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06}}; //Interface Y
const IID IID_Junk ={0x12345678,0x9876,0x5432,{0x12,0x34,0x56,0x78,0x98,0x76,0x54,0x32}}; //Junk Number So QueryInterface() Fails
HRESULT (__stdcall* ptrQueryInterface) (int, const IID&, void**);
ULONG (__stdcall* ptrAddRef) (int);
ULONG (__stdcall* ptrRelease) (int);
void (__stdcall* pIFn) (int,int);
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
int main(void)
{
IUnknown* pIUnk=NULL;
IClassFactory* pCF=NULL;
unsigned int* pVTbl=0;
unsigned int* VTbl=0;
CoInitialize(NULL);
CoGetClassObject(CLSID_CA,CLSCTX_INPROC_SERVER,NULL,IID_IClassFactory,(void**)&pCF);
pCF->CreateInstance(NULL,IID_IUnknown,(void**)&pVTbl);
pCF->Release();
printf("pVTbl = %u\n",pVTbl);
printf("\n");
printf("&pVTbl[i]\t&VTbl[i]\tVTbl[i]\t\tFunction Call Through Pointer\n");
printf("=============================================================================\n");
for(unsigned int i=0;i<=1;i++)
{
VTbl=(unsigned int*)pVTbl[i]; //Call...
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[0],VTbl[0]);
ptrQueryInterface=(HRESULT(__stdcall*)(int, const IID&, void**)) VTbl[0];
ptrQueryInterface((int)&pVTbl[i],IID_Junk,(void**)&pIUnk); //QueryInterface()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[1],VTbl[1]);
ptrAddRef=(ULONG(__stdcall*)(int)) VTbl[1];
ptrAddRef((int)&pVTbl[i]); //AddRef()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[2],VTbl[2]);
ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
ptrRelease((int)&pVTbl[i]); //Release()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[3],VTbl[3]);
pIFn=(void(__stdcall*)(int,int)) VTbl[3];
pIFn((int)&pVTbl[i],i); //Fx1() / Fy1()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[4],VTbl[4]);
pIFn=(void(__stdcall*)(int,int)) VTbl[4];
pIFn((int)&pVTbl[i],i); //Fx2() / Fy2()
printf("\n");
}
ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
ptrRelease((int)&pVTbl[0]);
CoUninitialize();
getchar();
return 0;
}
/*
Called CA::AddRef()
pVTbl = 10355664
&pVTbl[i] &VTbl[i] VTbl[i] Function Call Through Pointer
=============================================================================
10355664 268464396 268439696 Called CA::QueryInterface()
10355664 268464400 268439840 Called CA::AddRef()
10355664 268464404 268439872 Called CA::Release()
10355664 268464408 268439936 Called Fx1() : iNum = 0
10355664 268464412 268439968 Called Fx2() : iNum = 0
10355668 268464376 268440448 Called CA::QueryInterface()
10355668 268464380 268440464 Called CA::AddRef()
10355668 268464384 268440480 Called CA::Release()
10355668 268464388 268440000 Called Fy1() : iNum = 1
10355668 268464392 268440032 Called Fy2() : iNum = 1
Called CA::Release()
*/
The only significant change is that an IClassFactory* (IClassFactory Pointer) had to be declared, and instead of calling CoCreateInstance(), which returns a pointer to the IUnknown of class CA, we call CoGetClassObject() which returns an IUnknown pointer to CA's class factory, i.e., CAClassFactory. Again, COM system code must load the dll from paths found in the registry to do this, and do a GetProcAddress() or whatever to perform a call to DllGetClassObject() on the client's behalf. Then the client simply uses the returned pointer to the class factory – here pCF – to call CAClassFactory::CreateInstance(). By the way, the double colons in C++ are used here to specify that the function CreateInstance() is a member function of class CAClassFactory. Its as simple as that in C++.
IClassFactory* pCF=NULL;
CoInitialize(NULL);
CoGetClassObject(CLSID_CA, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory,(void**)&pCF);
pCF->CreateInstance(NULL,IID_IUnknown,(void**)&pVTbl);
pCF->Release();
One reason you might want to do this is if you are going to create multiple instances of the component you only need to create one class factory just once and use it to create as many components as you want. Under those circumstances it would be wasteful to use CoCreateInstance() multiple times.
It was a bit of a pain translating this to PowerBASIC. First I discovered that CoGetClassObject() wasn't declared in PowerBASIC's Win32Api.inc file along with CoCreateInstance. In fact, it didn't even seem to be in any file in the \Include directory of PowerBASIC. So I looked in Jose's includes and found it there. It really is an important function. To use it though we're going to have to create another function pointer declare such as we have with QueryInterface, AddRef, and release. There are some other issues too. Note in the above C++ code I passed in a NULL for the third parameter. That won't work with Jose's declare I don't believe so we'll need to pass in what the function really wants and that is a pointer to a COSERVERINFO structure. Lucky for us Jose translated that in his includes. Perhaps I'd better display the Api docs on CoGetClassObject()...
edit - 1/20/2008 @10:09 PM -Jose told me to just use Byval %NULL for the Null COSERVERINFO pointer. It works!
STDAPI CoGetClassObject
(
REFCLSID rclsid, //CLSID associated with the class object
DWORD dwClsContext, //Context for running executable code
COSERVERINFO* pServerInfo, //Pointer to machine on which the object is to be instantiated
REFIID riid, //Reference to the identifier of the interface
LPVOID* ppv //Address of output variable that receives the interface pointer requested in riid
);
Also, we need to define the IID for the IClassFactory interface and that is {00000001-0000-0000-C000-000000000046}. Here is the necessary equate...
$IID_IClassFactory =Guid$("{00000001-0000-0000-C000-000000000046}") 'Microsoft Defined - IClassFactory
Finally, we need to construct a suitable function pointer declare to call CAClassFactory::CreateInstance() once CoGetClassObject() returns a class factory pointer to us. I've found this to work...
Declare Function ptrCreateInstance (Byval this As Dword, ByVal pUnknown As Dword, Byref iid As Guid, Byref pVTbl As Any) As Long
Note that we need to pass in the required this pointer so as not to unbalance the stack. Below is the PowerBASIC program using CoGetClassObject(). You need to use Jose's includes for this.
#Compile Exe "CAClient.exe"
#Dim All
#Include "Win32Api.inc"
#Include "ObjBase.inc"
%CLSCTX_INPROC_SERVER =&H1???
$IID_IUnknown =Guid$("{00000000-0000-0000-C000-000000000046}") 'Microsoft Defined - IUnknown
$IID_IClassFactory =Guid$("{00000001-0000-0000-C000-000000000046}") 'Microsoft Defined - IClassFactory
$CLSID_CA =Guid$("{20000000-0000-0000-0000-000000000004}") 'Class ID of Class CA, ie., Class A
$IID_IX =Guid$("{20000000-0000-0000-0000-000000000005}") 'Interface X
$IID_IY =Guid$("{20000000-0000-0000-0000-000000000006}") 'Interface Y
$IID_Junk =Guid$("{12345678-9876-5432-1012-345678901234}") 'Junk Number So QueryInterface() Fails
Declare Function ptrCreateInstance (Byval this As Dword, Byval pUnknown As DWord, Byref iid As Guid, Byref pVTbl As Any) As Long
Declare Function ptrQueryInterface (Byval this As Dword, Byref iid As Guid, Byref pUnknown As Any) As Long
Declare Function ptrAddRef (Byval this As Dword) As Dword
Declare Function ptrRelease (Byval this As Dword) As Dword
Declare Function ptrFn (Byval this As Dword, ByVal iNum As Long) As Long
Interface I_X $IID_IX : Inherit IUnknown
Method Fx1(ByVal iNum As Long) As Long
Method Fx2(ByVal iNum As Long) As Long
End Interface
Interface I_Y $IID_IY : Inherit IUnknown
Method Fy1(ByVal iNum As Long) As Long
Method Fy2(ByVal iNum As Long) As Long
End Interface
Function PBMain() As Long
Local pVTbl,VTbl,pCF,CFVTbl,pUnk As Dword Ptr
Local hResult As Long
Register i As Long
hResult=CoGetClassObject($CLSID_CA, %CLSCTX_INPROC_SERVER, Byval %NULL, $IID_IClassFactory, pCF)
If SUCCEEDED(hResult) Then
Print "CoGetClassObject() Succeeded!"
Print "pCF = " pCF
CFVTbl=@pCF[0] '@CFVTbl[3] is the address of CAClassFactory::CreateInstance(...)
Call Dword @CFVTbl[3] Using ptrCreateInstance(pCF,pUnk,$IID_IUnknown,pVTbl) To hResult
If SUCCEEDED(hResult) Then 'If we get inside this If the class factory has alreadt created class CA
Print "pCF->CreateInstance() Succeeded!" 'so we can do a pCF->Release(). Release() is in slot #2
Call DWord @CFVTbl[2] Using ptrRelease(pCF) To hResult 'Release() CAClassFactory
Print "pVTbl = " pVTbl 'We now have the same VTbl Ptr ( pVTbl ) we originally got from
Print 'CoCreateInstance(), so go to town.....
Print "Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword"
Print "==============================================================================="
For i=0 To 1
VTbl=@pVTbl[i] 'Call...
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[0]) Tab(37)@VTbl[0] " ";
Call DWord @VTbl[0] Using ptrQueryInterface(Varptr(@pVTbl[i]), $IID_Junk, pUnk) To hResult 'QueryInterface()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[1]) Tab(37)@VTbl[1] " ";
Call DWord @VTbl[1] Using ptrAddRef(Varptr(@pVTbl[i])) To hResult 'AddRef()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[2]) Tab(37)@VTbl[2] " ";
Call DWord @VTbl[2] Using ptrRelease(Varptr(@pVTbl[i])) To hResult 'Release()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[3]) Tab(37)@VTbl[3] " ";
Call DWord @VTbl[3] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fx1() / Fy1()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[4]) Tab(37)@VTbl[4] " ";
Call DWord @VTbl[4] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fy1() / Fy2()
Print
Next i
Call DWord @VTbl[2] Using ptrRelease(pVTbl) To hResult
End If
End If
Waitkey$
PBMain=0
End Function
'Called CAClassFactory::AddRef()
'Called CAClassFactory::AddRef()
'Called CAClassFactory::Release()
'Called CAClassFactory::AddRef()
'Called CAClassFactory::Release()
'CoGetClassObject() Succeeded!
'pCF = 14484944
'this = 14484880
'sizeof(CA) = 12
'Called CA::AddRef()
'pCF->CreateInstance() Succeeded!
'Called CAClassFactory::Release()
'pVTbl = 14484880
'
'Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword
'===============================================================================
'14484880 14381324 14356624 Called CA::QueryInterface()
'14484880 14381328 14356768 Called CA::AddRef()
'14484880 14381332 14356800 Called CA::Release()
'14484880 14381336 14356864 Called Fx1() : iNum = 0
'14484880 14381340 14356896 Called Fx2() : iNum = 0
'
'14484884 14381304 14357392 Called CA::QueryInterface()
'14484884 14381308 14357408 Called CA::AddRef()
'14484884 14381312 14357424 Called CA::Release()
'14484884 14381316 14356928 Called Fy1() : iNum = 1
'14484884 14381320 14356960 Called Fy2() : iNum = 1
'
'Called CA::Release()
Well, there you have it. I'd like to continue and show how we can do away with system COM services entirely and just load the COM class in the dll ourselves with LoadLibrary() and GetProcAddress(), but I believe I'll leave that for later and just provide some closing thoughts.
It is certainly not necessary to go through all this that I have done here to access a COM object. It is interesting to compare minimal programs side by side in PowerBASIC and C++ that access the I_X and I_Y interfaces of ComObject.CA that we have been examining under a microscope. Here is a minimal PowerBASIC program with output followed by the exact same thing in C++...
#Compile Exe "CAClient.exe"
#Dim All
#Include "Win32Api.inc"
'#Include "ObjBase.inc"
$CLSID_CA =Guid$("{20000000-0000-0000-0000-000000000004}") 'Class ID of Class CA, ie., Class A
$IID_IX =Guid$("{20000000-0000-0000-0000-000000000005}") 'Interface X
$IID_IY =Guid$("{20000000-0000-0000-0000-000000000006}") 'Interface Y
Interface I_X $IID_IX : Inherit IUnknown
Method Fx1(ByVal iNum As Long) As Long
Method Fx2(ByVal iNum As Long) As Long
End Interface
Interface I_Y $IID_IY : Inherit IUnknown
Method Fy1(ByVal iNum As Long) As Long
Method Fy2(ByVal iNum As Long) As Long
End Interface
Function PBMain() As Long 'Important Note! I had originally made
Local hResult As Long 'a mistake in my original posting of this
Local ix As I_X 'little program that Jose caught! In
Local iy As I_Y 'order to call the I_Y interface functions...
ix=NewCom Clsid $CLSID_CA 'I had done this > iy=NewCom("ComObject.CA")
hResult=ix.Fx1(25) 'That isn't necessary! PowerBASIC does,
hResult=ix.Fx2(50) 'behind the scenes, so to speak, a
iy=ix ''iy=NewCom("ComObject.CA") 'QueryInterface() on I_Y using I_X exactly
hResult=iy.Fy1(75) 'as I had done in the C++ program below!
hResult=iy.Fy2(100) 'This is actually one of the fundamental
Set ix = Nothing 'rules of COM. If you have an interface
Set iy = Nothing 'on an object you should be able to get
Waitkey$ 'any other interface (if you have its IID).
PBMain=0
End Function
'===Output==================
'Called CA::AddRef()
'Called CA::AddRef()
'Called CA::Release()
'Called CA::AddRef()
'Called CA::Release()
'Called Fx1() : iNum = 25
'Called Fx2() : iNum = 50
'Called CA::AddRef()
'Called Fy1() : iNum = 75
'Called Fy2() : iNum = 100
'Called CA::Release()
'Called CA::Release()
//C++ Version
#include <windows.h> //CAClient6
#include <stdio.h>
static const CLSID CLSID_CA = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04}};
static const IID IID_I_X = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05}};
static const IID IID_I_Y = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06}};
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
int main(void)
{
I_X* pIX=NULL;
I_Y* pIY=NULL;
HRESULT hr;
hr=CoInitialize(NULL);
hr=CoCreateInstance(CLSID_CA, NULL, CLSCTX_INPROC_SERVER, IID_I_X, (void**)&pIX);
hr=pIX->Fx1(25);
hr=pIX->Fx2(50);
hr=pIX->QueryInterface(IID_I_Y,(void**)&pIY);
hr=pIY->Fy1(75);
hr=pIY->Fy2(100);
hr=pIX->Release();
hr=pIY->Release();
CoUninitialize();
return 0;
}
/*===Output=================
sizeof(CA) = 12
this = 10355696
Called CA::AddRef()
Called CA::AddRef()
Called CA::Release()
Called CA::AddRef()
Called CA::Release()
Called Fx1() : iNum = 25
Called Fx2() : iNum = 50
Called CA::AddRef()
Called Fy1() : iNum = 75
Called Fy2() : iNum = 100
Called CA::Release()
Called CA::Release()
*/
They are almost the same exact number of lines even! Interestingly, in the PowerBASIC program class CA apparently had to be created twice, as that is what the output shows, whereas in the C++ program I just did a QueryInterface() for I_Y off of the I_X interface I already had. Upon examining this issue in the PowerBASIC documentation it seems to state I could have used GetCom instead of AnyCom or NewCom if I had implemented the IDispatch interface in the object, but as you would know if you examined my C++ code for the COM object at all, the IDispatch interface wasn't implemented. I'm just mentioning this as a curiosity and don't consider it of any major import – at least not to me.
Finally, I'd like to address the question of what I got out of this exceedingly long endeavor. Well, if nothing else I've an exceedingly good idea now of what is happening behind the scenes in C++ code when I see something like this...
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv)
{
if(iid==IID_IX)
*ppv=static_cast<IX*>(this);
else if(iid==IID_IY)
*ppv=static_cast<IY*>(this);
else if(iid==IID_IUnknown)
*ppv=static_cast<IX*>(this);
else
{
*ppv=NULL;
return E_NOINTERFACE;
}
static_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
(The exceedingly good idea I have about it is that the pointer stored in ppv is being set to the address of the requested Vtable).
Jose found some mistakes. >:(
But I fixed them already! :)
By the way, I made some comments regarding my difficulties creating the C++ COM dll with the GNU compilers that are used by the Dev-C++ and new CodeBlocks development systems. This comment doesn't apply to the client programs at all. There is no problem whatsoever with that. If you register the dll with regsvr32 you'll be able to use those GNU compilers for the client programs.
I might point out I probably am not following the best practices in the way I associated GUIDs to the interfaces. I didn't use GuidGen or any of the other tools. I just made up the numbers 20000000-0000-0000-0000-00000004, 20000000-0000-0000-0000-00000005, and 20000000-0000-0000-0000-00000006. So it probably wouldn't hurt to check your registry that those numbers don't overwrite anything (You need to expand your CLSID key and search through your millions of numbers. They are in ascending order. If you don't know what I'm talking about you need to learn that before you start learning COM - only a few months ago I didn't know it).
Frederick,
Excellent stuff. I had just started investigating accessing PowerBASIC created COM servers using c or c++.
I have not delved too deeply into your toutorial yet (but I will).
Does there exist a utility similar to the PB com browsers that will create an include file from a typelib for c/c++?
James
Fred,
Just a heads up.
I used Borland's free c++ compiler 5.5 with RADasm and the client1 cpp code worked flawlessly.
James
Hi James
Thanks for the feedback. I have a good bit of code written to create procedure declarations for C/C++. I was doing it just to learn the whole TypeLib territory working with the interfaces, etc. I figured there probably were a multitude of tools out there to do it from the C/C++ angle.
When I get time I'm going to explore further the difficulties I was having with the GNU compilers. I really see no reason why they won't work. Actually, they sometimes do work creating the COM object. But I have to work on 'real' work right now!
Anyway, that's something I'd like to work on sometime if I have a little time, i.e., a TypeBib Browser with a radio button that produces either PB or C++ includes.
Fred,
I know you mentioned covering this in Part 2 but would it be possible to get the c++ code to load
an unregistered com object (using your CA.DLL example) from a dll? My c++ knowledge is very poor at best.
This is the José PB code I used before the functionality was added to the compiler.
Thank You,
James
' ========================================================================================
' Loads the specified library and creates an instance of an object.
' If it succeeds, returns a reference to the requested interface; otherwise, it returns null.
' Note: Do not use FreeLibrary to unload the library once you have got a valid reference
' to an interface or your application will GPF. Before calling FreeLibrary, all the
' interface references must be released. If you don't need to unload the library until
' the application ends, then you don't need to call FreeLibrary because PB calls
' CoUninitialize under the hood, that closes the COM library on the current thread,
' unloads all DLLs loaded by the thread, frees any other resources that the thread
' maintains, and forces all RPC connections on the thread to close.
' ========================================================================================
FUNCTION CreateInstanceFromDll ( _
BYVAL strLibName AS STRING _ ' // [in] Library name and path
, BYREF rclsid AS GUID _ ' // [in] CLSID that will associate the correct data and code
, BYREF riid AS GUID _ ' // [in] IID of the interface to be used to communicate with the newly created objec
, OPTIONAL BYVAL strLicKey AS STRING _ ' // [in] License key (ansi string)
) AS IUnknown ' // [out] The interface pointer requested in riid
LOCAL hr AS LONG ' // HRESULT
LOCAL hLib AS DWORD ' // Library handle
LOCAL pProc AS DWORD ' // Procedure address
LOCAL pIClassFactory AS IClassFactory ' // IClassFactory interface
LOCAL pIClassFactory2 AS IClassFactory2 ' // IClassFactory2 interface
LOCAL ppv AS IUnknown ' // The requested interface pointer
' // See if the library is already loaded in the address space
hLib = GetModuleHandle(BYCOPY strLibName)
' // If it is not loaded, load it
IF hLib = %NULL THEN hLib = LoadLibrary(BYCOPY strLibName)
' // If it fails, abort
IF ISFALSE hLib THEN EXIT FUNCTION
' // Retrieve the address of the exported function DllGetClassObject
pProc = GetProcAddress(hLib, "DllGetClassObject")
IF ISFALSE pProc THEN
FreeLibrary hLib
EXIT FUNCTION
END IF
IF LEN(strLicKey) = 0 THEN
' // Request a reference to the IClassFactory interface
CALL DWORD pProc USING DllGetClassObject(rclsid, $IID_IClassFactory, pIClassFactory) TO hr
IF hr <> %S_OK THEN
FreeLibrary hLib
EXIT FUNCTION
END IF
' // Create an instance of the server or control
hr = pIClassFactory.CreateInstance(%NULL, riid, BYVAL VARPTR(ppv))
IF hr <> %S_OK THEN EXIT FUNCTION
ELSE
' // Request a reference to the IClassFactory2 interface
CALL DWORD pProc USING DllGetClassObject(rclsid, $IID_IClassFactory2, pIClassFactory2) TO hr
IF hr <> %S_OK THEN
FreeLibrary hLib
EXIT FUNCTION
END IF
' // Create a licensed instance of the server or control
strLicKey = UCODE$(strLicKey)
hr = pIClassFactory2.CreateInstanceLic(NOTHING, NOTHING, riid, strLicKey, ppv)
IF hr <> %S_OK THEN EXIT FUNCTION
END IF
FUNCTION = ppv
END FUNCTION
My confession: Never even tried it! Just assummed it would work. Just banged this out and luckily it does!
(you need to change the path to where you have CA.dll in LoadLibrary() call at top)
//CAClient3.cpp
#include <windows.h> //CAClient
#include <stdio.h> //IID_IClassFactory={00000001-0000-0000-C000-000000000046};
const CLSID CLSID_CA ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04}}; //Class ID of Class CA, ie., Class A
const IID IID_I_X ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05}}; //Interface X
const IID IID_I_Y ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06}}; //Interface Y
const IID IID_Junk ={0x12345678,0x9876,0x5432,{0x12,0x34,0x56,0x78,0x98,0x76,0x54,0x32}}; //Junk Number So QueryInterface() Fails
HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);
HRESULT (__stdcall* ptrQueryInterface) (int, const IID&, void**);
ULONG (__stdcall* ptrAddRef) (int);
ULONG (__stdcall* ptrRelease) (int);
void (__stdcall* pIFn) (int,int);
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
int main(void)
{
IClassFactory* pCF=NULL;
HMODULE hDll=NULL;
BOOL blnFree =FALSE;
IUnknown* pIUnk=NULL;
unsigned int* pVTbl=0;
unsigned int* VTbl=0;
HRESULT hr;
hDll=LoadLibrary("C:\\Code\\VStudio\\VC++6\\Projects\\COM\\ComObjects\\CA\\Release\\CA.dll");
printf("hDll=%u\n",hDll);
if(hDll)
{
ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
printf("ptrDllGetClassObject = %u\n",ptrDllGetClassObject);
hr=ptrDllGetClassObject(CLSID_CA,IID_IClassFactory,(void**)&pCF);
if(SUCCEEDED(hr))
{
puts("Worked!");
pCF->CreateInstance(NULL,IID_IUnknown,(void**)&pVTbl);
pCF->Release();
printf("pVTbl = %u\n",pVTbl);
printf("\n");
printf("&pVTbl[i]\t&VTbl[i]\tVTbl[i]\t\tFunction Call Through Pointer\n");
printf("=============================================================================\n");
for(unsigned int i=0;i<=1;i++)
{
VTbl=(unsigned int*)pVTbl[i]; //Call...
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[0],VTbl[0]);
ptrQueryInterface=(HRESULT(__stdcall*)(int, const IID&, void**)) VTbl[0];
ptrQueryInterface((int)&pVTbl[i],IID_Junk,(void**)&pIUnk); //QueryInterface()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[1],VTbl[1]);
ptrAddRef=(ULONG(__stdcall*)(int)) VTbl[1];
ptrAddRef((int)&pVTbl[i]); //AddRef()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[2],VTbl[2]);
ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
ptrRelease((int)&pVTbl[i]); //Release()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[3],VTbl[3]);
pIFn=(void(__stdcall*)(int,int)) VTbl[3];
pIFn((int)&pVTbl[i],i); //Fx1() / Fy1()
printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[4],VTbl[4]);
pIFn=(void(__stdcall*)(int,int)) VTbl[4];
pIFn((int)&pVTbl[i],i); //Fx2() / Fy2()
printf("\n");
}
ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
ptrRelease((int)&pVTbl[0]);
getchar();
}
blnFree=FreeLibrary(hDll);
printf("blnFree=%u\n",blnFree);
}
return 0;
}
Let me know if that works Jim! You had mentioned you had ver 5.5 of Borland's C++ product. I actually have the standard edition version 5. I almost never used it, as right after that I obtained Microsoft's high octane version through my employer, and I'm still using that.
I've had a love/hate relationship with C++ for years. After I struggled teaching myself C way back in the nineties I was looking forward to learning C++. When I started learning about classes and all that stuff I thought it was really neat. Then I bumbed up hard against MFC and that stoped me dead in my tracks. I truely hated it. I just simply don't like class libraries that wrap the Windows Api. At the time though I wasn't prescient enough to make the distinction between C++ and class libraries that wrap the Windows Api; all I knew was that I hated MFC and C++ was associated with it rather intimately, and I more or less threw out the baby with the bathwater. As time went on though it dawned on me that the type of raw Windows Api programming I do with PowerBASIC I could do just as well in C++, the only real problem being the lack of dynamic string handling abilities in native unadorned C++. Yes, C++ has string class libraries, but as far as I know you need to link with the horrible big and bloated MFC.dll to use them, and under no circumstances was I ever about to do that. So eventually I got good enough with it to write my own string class, and I use that now in C++ apps. So really, I've the best of both worlds, i.e., a super powerful OOP implementation, plus I can write small executables just like in PowerBASIC.
At work I pretty much have to use C++ for my data recorder work but I only use PowerBASIC for desktop work - not much C++ except for learning this COM stuff.
....back to the program though...most C++ folks (all C++ folks!?!) use typedefs to cast the return from GetProcAddress() to a usable function pointer. I don't usually (I'll be honest - I never do it) do that because typedefs confuse me more than they help me. The way I see the issue is that if I create a typedef it cleans up the point of call, but it introduces 'magic' in my programs. If I don't use a typedef its ugly as sin at the point of call but at least there's no witchcraft involved!
I added a bunch of printf statements within the dll, recompiled it, then ran this PowerBASIC program that uses GetProcAddress() instead of COM services to load the dll. It seems to work OK.
#Compile Exe "CAClient3.exe"
#Dim All
#Include "Win32Api.inc"
#Include "ObjBase.inc"
%CLSCTX_INPROC_SERVER =&H1???
$IID_IUnknown =Guid$("{00000000-0000-0000-C000-000000000046}") 'Microsoft Defined - IUnknown
$IID_IClassFactory =Guid$("{00000001-0000-0000-C000-000000000046}") 'Microsoft Defined - IClassFactory
$CLSID_CA =Guid$("{20000000-0000-0000-0000-000000000004}") 'Class ID of Class CA, ie., Class A
$IID_IX =Guid$("{20000000-0000-0000-0000-000000000005}") 'Interface X
$IID_IY =Guid$("{20000000-0000-0000-0000-000000000006}") 'Interface Y
$IID_Junk =Guid$("{12345678-9876-5432-1012-345678901234}") 'Junk Number So QueryInterface() Fails
Declare Function DllGetClassObject (Byref Clsid As Guid, Byref iid As Guid, Byref pIUnknown As Any) As Long
Declare Function ptrCreateInstance (Byval this As Dword, Byref pUnknown As Any, Byref iid As Guid, Byref pVTbl As Any) As Long
Declare Function ptrQueryInterface (Byval this As Dword, Byref iid As Guid, Byref pUnknown As Any) As Long
Declare Function ptrAddRef (Byval this As Dword) As Dword
Declare Function ptrRelease (Byval this As Dword) As Dword
Declare Function ptrFn (Byval this As Dword, ByVal iNum As Long) As Long
Interface I_X $IID_IX : Inherit IUnknown
Method Fx1(ByVal iNum As Long) As Long
Method Fx2(ByVal iNum As Long) As Long
End Interface
Interface I_Y $IID_IY : Inherit IUnknown
Method Fy1(ByVal iNum As Long) As Long
Method Fy2(ByVal iNum As Long) As Long
End Interface
Function PBMain() As Long
Local pVTbl,VTbl,pCF,CFVTbl,pUnk,pDllProc As Dword Ptr
Local pSerInfo As COSERVERINFO
Local hResult As Long
Local hDll As DWord
Register i As Long
hDll=LoadLibrary("C:\Code\VStudio\VC++6\Projects\COM\ComObjects\CA\Release\CA.dll") 'Change This Line!!!!
'hDll=LoadLibrary("C:\Code\DevC++\CA\CA.dll")
If hDll Then
Print "hDll = " hDll
pDllProc=GetProcAddress(hDll,"DllGetClassObject")
If pDllProc Then
Print "pDllProc = " pDllProc
Call DWord pDllProc Using DllGetClassObject($CLSID_CA, $IID_IClassFactory, pCF) To hResult
'hResult=CoGetClassObject($CLSID_CA, %CLSCTX_INPROC_SERVER, pSerInfo, $IID_IClassFactory, pCF)
If SUCCEEDED(hResult) Then
Print "Successfully Loaded CA.dll And Succeeded In DllGetClassObject() Call!"
Print "pCF = " pCF
CFVTbl=@pCF[0] '@CFVTbl[3] is the address of CAClassFactory::CreateInstance(...)
Call Dword @CFVTbl[3] Using ptrCreateInstance(pCF,pUnk,$IID_IUnknown,pVTbl) To hResult
If SUCCEEDED(hResult) Then 'If we get inside this If the class factory has alreadt created class CA
Print "pCF->CreateInstance() Succeeded!" 'so we can do a pCF->Release(). Release() is in slot #2
Call DWord @CFVTbl[2] Using ptrRelease(pCF) To hResult 'Release() CAClassFactory
Print "pVTbl = " pVTbl 'We now have the same VTbl Ptr ( pVTbl ) we originally got from
Print 'CoCreateInstance(), so go to town.....
Print "Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword"
Print "==============================================================================="
For i=0 To 1
VTbl=@pVTbl[i] 'Call...
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[0]) Tab(37)@VTbl[0] " ";
Call DWord @VTbl[0] Using ptrQueryInterface(Varptr(@pVTbl[i]), $IID_Junk, pUnk) To hResult 'QueryInterface()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[1]) Tab(37)@VTbl[1] " ";
Call DWord @VTbl[1] Using ptrAddRef(Varptr(@pVTbl[i])) To hResult 'AddRef()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[2]) Tab(37)@VTbl[2] " ";
Call DWord @VTbl[2] Using ptrRelease(Varptr(@pVTbl[i])) To hResult 'Release()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[3]) Tab(37)@VTbl[3] " ";
Call DWord @VTbl[3] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fx1() / Fy1()
Print LTrim$(Str$(Varptr(@pVTbl[i]))) Tab(19)Varptr(@VTbl[4]) Tab(37)@VTbl[4] " ";
Call DWord @VTbl[4] Using ptrFn(Varptr(@pVTbl[i]),i) To hResult 'Fy1() / Fy2()
Print
Next i
Call DWord @VTbl[2] Using ptrRelease(pVTbl) To hResult
End If
End If
FreeLibrary(hDll)
End If
End If
Waitkey$
PBMain=0
End Function
'hDll = 268435456
'pDllProc = 268441664
'
'Entering DllGetClassObject()
'Called CAClassFactory Constructor!
'Called CAClassFactory::QueryInterface()
'Called CAClassFactory::AddRef()
'Leaving DllGetClassObject()
'
'Successfully Loaded CA.dll And Succeeded In DllGetClassObject() Call!
'pCF = 8521248
'Called CAClassFactory::CreateInstance()
'this = 8521216
'sizeof(CA) = 12
'Called CA::AddRef()
'pCF->CreateInstance() Succeeded!
'Called CAClassFactory::Release()
'Called CAClassFactory Destructor!
'pVTbl = 8521216
'
'Varptr(@pVTbl[i]) Varptr(@VTbl[i]) @VTbl[i] Function Call With Call Dword
'===============================================================================
'8521216 268464396 268439696 Called CA::QueryInterface()
'8521216 268464400 268439840 Called CA::AddRef()
'8521216 268464404 268439872 Called CA::Release()
'8521216 268464408 268439936 Called Fx1() : iNum = 0
'8521216 268464412 268439968 Called Fx2() : iNum = 0
'
'8521220 268464376 268440528 Called CA::QueryInterface()
'8521220 268464380 268440544 Called CA::AddRef()
'8521220 268464384 268440560 Called CA::Release()
'8521220 268464388 268440000 Called Fy1() : iNum = 1
'8521220 268464392 268440032 Called Fy2() : iNum = 1
'
'Called CA::Release()
I modified your c++ Client6 code and it works. I'm not sure if it's leaking or not though.
James
#include <windows.h> //CAClient6
#include <stdio.h>
static const CLSID CLSID_CA = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04}};
static const IID IID_I_X = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05}};
static const IID IID_I_Y = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06}};
HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
int main(void)
{
I_X* pIX=NULL;
I_Y* pIY=NULL;
HRESULT hr;
IClassFactory* pCF=NULL;
HMODULE hDll=NULL;
BOOL blnFree =FALSE;
hr=CoInitialize(NULL);
hDll=LoadLibrary("D:\\ComTutorial1\\Server\\bin\\CA.dll");
ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
printf("ptrDllGetClassObject = %u\n",ptrDllGetClassObject);
hr=ptrDllGetClassObject(CLSID_CA,IID_IClassFactory,(void**)&pCF);
if(SUCCEEDED(hr))
{
pCF->CreateInstance(NULL,IID_I_X,(void**)&pIX);
// is this needed ??
pCF->Release();
hr=pIX->Fx1(25);
hr=pIX->Fx2(50);
hr=pIX->QueryInterface(IID_I_Y,(void**)&pIY);
hr=pIY->Fy1(75);
hr=pIY->Fy2(100);
hr=pIX->Release();
hr=pIY->Release();
}
CoUninitialize();
blnFree=FreeLibrary(hDll);
getchar();
return 0;
}
Yes, the pCF->Release() is needed because CAClassFactory::QueryInterface() increments the reference count when it gets called. If a Release() isn't called then I think memory would leak. What I don't believe you need are the CoInitialize() and CoUninitialize() as we aren't using Windows COM Services (SCM - service control manager) to do anything for us.
As I had mentioned, I added a bunch of printf statements to the dll and recompiled. Using this version and running your program with and without the pCF->Release() yields the following results. Notice the CAClassFactory destructor isn't called without the Release() in the second run...
#include <windows.h> //CAClient9
#include <stdio.h>
static const CLSID CLSID_CA = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04}};
static const IID IID_I_X = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05}};
static const IID IID_I_Y = {0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06}};
HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
int main(void)
{
I_X* pIX=NULL;
I_Y* pIY=NULL;
HRESULT hr;
IClassFactory* pCF=NULL;
HMODULE hDll=NULL;
hDll=LoadLibrary("C:\\Code\\VStudio\\VC++6\\Projects\\COM\\ComObjects\\CA\\Release\\CA.dll");
//hDll=LoadLibrary("C:\\Code\\VStudio\\CA\\Release\\CA.dll");
ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
printf("ptrDllGetClassObject = %u\n",ptrDllGetClassObject);
hr=ptrDllGetClassObject(CLSID_CA,IID_IClassFactory,(void**)&pCF);
if(SUCCEEDED(hr))
{
pCF->CreateInstance(NULL,IID_I_X,(void**)&pIX);
// is this needed ??
pCF->Release();
hr=pIX->Fx1(25);
hr=pIX->Fx2(50);
hr=pIX->QueryInterface(IID_I_Y,(void**)&pIY);
hr=pIY->Fy1(75);
hr=pIY->Fy2(100);
hr=pIX->Release();
hr=pIY->Release();
}
FreeLibrary(hDll);
getchar();
return 0;
}
/*
====Output Including pCF->Release=======
========================================
ptrDllGetClassObject = 268441664
Entering DllGetClassObject()
Called CAClassFactory Constructor!
Called CAClassFactory::QueryInterface()
Called CAClassFactory::AddRef()
Leaving DllGetClassObject()
Called CAClassFactory::CreateInstance()
this = 10421168
sizeof(CA) = 12
Called CA::AddRef()
Called CAClassFactory::Release()
Called CAClassFactory Destructor!
Called Fx1() : iNum = 25
Called Fx2() : iNum = 50
Called CA::AddRef()
Called Fy1() : iNum = 75
Called Fy2() : iNum = 100
Called CA::Release()
Called CA::Release()
*/
/*
====Output With No pCF->Release()========
=========================================
ptrDllGetClassObject = 268441664
Entering DllGetClassObject()
Called CAClassFactory Constructor!
Called CAClassFactory::QueryInterface()
Called CAClassFactory::AddRef()
Leaving DllGetClassObject()
Called CAClassFactory::CreateInstance()
this = 10421168
sizeof(CA) = 12
Called CA::AddRef()
Called Fx1() : iNum = 25
Called Fx2() : iNum = 50
Called CA::AddRef()
Called Fy1() : iNum = 75
Called Fy2() : iNum = 100
Called CA::Release()
Called CA::Release()
*/
So COM objects are created in two steps (three, if you want to include calling DllGetClassObject()). First, the class factory is created through some kind of memory allocation - in a C++ program probably through the 'new' operator. At that time there will be a QueryInterface call that will increment the reference count on the class factory. Then, once the class factory is created, the client can call CAClassFactory::CreateInstance() to create the container class - in this case CA. At that point there will be another memory allocation (actually, quite a few of them) for that object. And there will still be two objects in memory - CA and CA's ClassFactory. If you hsve no further need of creating additional CAs, then the class factory should be released. In the Release() call the counter member is checked and if it is zero the delete operator is called and that causes the CAClassFactory destructor to execute.
.....but, I just thought of something cool. Will give it a try....
Well I thought I'd give this a try.
Created the server with PB
I used GUIDGEN.
James
PbServer Code:
'SED_PBWIN
#COMPILE DLL "PBCA.DLL"
#DIM ALL
#INCLUDE ONCE "Windows.inc"
$CLSID_CA =GUID$("{AB2D3628-5A59-4719-970B-1D6D70761AE8}")
'0xab2d3628, 0x5a59, 0x4719, 0x97, 0xb, 0x1d, 0x6d, 0x70, 0x76, 0x1a, 0xe8);
$IID_I_X = GUID$("{77E3ACD0-E12B-428c-A8E8-E32526F14DD4}")
'0x77e3acd0, 0xe12b, 0x428c, 0xa8, 0xe8, 0xe3, 0x25, 0x26, 0xf1, 0x4d, 0xd4);
$IID_I_Y = GUID$("{68DDC584-EC29-4214-A907-0A3FAD7D7E32}")
'0x68ddc584, 0xec29, 0x4214, 0xa9, 0x7, 0xa, 0x3f, 0xad, 0x7d, 0x7e, 0x32);
CLASS CA $CLSID_CA AS COM
CLASS METHOD CREATE
STDOUT "Constructor Called"
END METHOD
CLASS METHOD DESTROY
STDOUT "Destructor Called"
END METHOD
INTERFACE I_X $IID_I_X : 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
END INTERFACE
INTERFACE I_Y $IID_I_Y : INHERIT IUNKNOWN
METHOD Fy1(BYVAL iNum AS LONG)
STDOUT "Called Fy1 iNum = " + FORMAT$(iNum)
END METHOD
METHOD Fy2(BYVAL iNum AS LONG)
STDOUT "Called Fy2 iNum = " + FORMAT$(iNum)
END METHOD
END INTERFACE
END CLASS
'==============================================================================
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
'==============================================================================
c++ client code
#include <windows.h>
#include <stdio.h>
static const CLSID CLSID_CA = {0xab2d3628, 0x5a59, 0x4719,{ 0x97, 0xb, 0x1d, 0x6d, 0x70, 0x76, 0x1a, 0xe8}};
static const IID IID_I_X = {0x77e3acd0, 0xe12b, 0x428c,{ 0xa8, 0xe8, 0xe3, 0x25, 0x26, 0xf1, 0x4d, 0xd4}};
static const IID IID_I_Y = {0x68ddc584, 0xec29, 0x4214,{0xa9, 0x7, 0xa, 0x3f, 0xad, 0x7d, 0x7e, 0x32}};
HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);
interface I_X : IUnknown
{
virtual HRESULT __stdcall Fx1(int)=0;
virtual HRESULT __stdcall Fx2(int)=0;
};
interface I_Y : IUnknown
{
virtual HRESULT __stdcall Fy1(int)=0;
virtual HRESULT __stdcall Fy2(int)=0;
};
int main(void)
{
I_X* pIX=NULL;
I_Y* pIY=NULL;
HRESULT hr;
IClassFactory* pCF=NULL;
HMODULE hDll=NULL;
BOOL blnFree =FALSE;
//Change Path Here
hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBCA.DLL");
ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
printf("ptrDllGetClassObject = %u\n",ptrDllGetClassObject);
hr=ptrDllGetClassObject(CLSID_CA,IID_IClassFactory,(void**)&pCF);
if(SUCCEEDED(hr))
{
pCF->CreateInstance(NULL,IID_I_X,(void**)&pIX);
// is this needed ??
pCF->Release();
hr=pIX->Fx1(25);
hr=pIX->Fx2(50);
hr=pIX->QueryInterface(IID_I_Y,(void**)&pIY);
hr=pIY->Fy1(75);
hr=pIY->Fy2(100);
hr=pIX->Release();
hr=pIY->Release();
}
blnFree=FreeLibrary(hDll);
getchar();
return 0;
}
Hi Jim!
Fun stuff, Huh? Its near my bed time so I don't expect I'll give your latest a try 'till tomorrow, but its my guess that if you take the memory apart and examine it you'll find out that PB arranges the VTable pointers somewhat differently. I do believe there are parts of this COM stuff that Microsoft has more or less etched in stone, and other parts that are not specified in the standard. What I expect is that PB will not locate the pVTbl pointers adjacent to each other like the C++ compilers will. I took especial note of this in my Objptr demo in PowerBASIC Source code and also in the 1st of my three posts. Do an ObjPtr() on I_X and I_Y and its my guess they are not in adjacent 4 byte slots like in the C++ code of my demo? I don't suppose it really matters. This is just something more or less anomalous that I noted.
I kind of got started on it at one point in my article, but then veered into something else; C++ came before COM by a good number of years, and when Microsoft was casting about for a memory architecture that would suit their design intent of OLE, it was the pVTbl - VTbl setup that most C++ implementations were creating that caught their attention. They settled on that as being something that would so isolate the client from the implementation details of the Server that the server would look to the client like the blackest of black boxes. It actually looks to me like three levels of indirection - not two.
This weekend I'd like to clean up some minor problems here and there in the code & article, and hopefully provide a version of the dll in the download that prints out a message for every procedure in the component. I think that would be useful for folks trying to understand the sequence in which all the calls are made.
Another little test.
I had more trouble than I should by forgetting to keep the exact same order of the declares!!
If you would rather I didn't clutter up your tutorial with my tests just say so.
James
PbServer
'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}")
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
METHOD iNum2_Get(iNum1 AS LONG)
iNum1 = iNum2
STDOUT "Called iNum2_Get = " + FORMAT$(iNum1)
END METHOD
METHOD iNum2_Set(BYVAL iNum AS LONG)
iNum2 = iNum
STDOUT "Called iNum2_Set = " + FORMAT$(iNum)
END METHOD
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 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
'==============================================================================
c++ client
#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 HRESULT __stdcall iNum2_Get(int*);
virtual HRESULT __stdcall iNum2_Set(int)=0;
virtual HRESULT __stdcall S1_Get(BSTR*);
virtual HRESULT __stdcall S1_Set(BSTR*);
};
int main(void)
{
iTest1* piTest01=NULL;
HRESULT hr;
IClassFactory* pCF=NULL;
HMODULE hDll=NULL;
int iNum2;
BSTR str1;
//Change Path Here
hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBCTEST01.DLL");
ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
printf("ptrDllGetClassObject = %u\n",ptrDllGetClassObject);
hr=ptrDllGetClassObject(CLSID_cTest01,IID_IClassFactory,(void**)&pCF);
if(SUCCEEDED(hr))
{
pCF->CreateInstance(NULL,IID_iTest01,(void**)&piTest01);
pCF->Release();
hr=piTest01->Fx1(25);
hr=piTest01->Fx2(50);
hr=piTest01->iNum2_Get(&iNum2);
printf("iNum2 = %u\n",iNum2);
hr=piTest01->iNum2_Set(22);
hr=piTest01->iNum2_Get(&iNum2);
printf("iNum2 = %u\n",iNum2);
hr=piTest01->S1_Get(&str1);
printf("str1 = %s\n",str1);
SysFreeString(str1);
str1 = SysAllocStringByteLen("Fuller",6);
hr=piTest01->S1_Set(&str1);
SysFreeString(str1);
hr=piTest01->S1_Get(&str1);
printf("str1 = %s\n",str1);
SysFreeString(str1);
hr=piTest01->Release();
}
FreeLibrary(hDll);
getchar();
return 0;
}
I don't mind at all Jim. I'm glad you've found the topic useful.
I see you've used the BSTR type in the C++ code James to access the PowerBASIC String. I guess that's the way it should be.
Got me to thinking...
Perhaps its time to deprecate my class CA and come up with a CA Extended that has interfaces with String data types! After all, its been out nearly a full week already right?
The thing to do would be to create I_X2 and an I_Y2 interfaces that inherit from I_X and I_Y.
Actually, this weekend I've been not working at the computer too much, but rather agonizing over a rather painful observation concerning the outputs of the many programs I've presented here - something of anomalous behaviour I believe. I'm surprised no one has spotted it.
Fred,
If you wouldn't mind I would like to add this link of a good article by Jeff Glatt, where he breaks down creating COM in plain C. Its just another perspective of your work here explaining basically the same with different words and slightly different approach. He has broken it down in 7-parts as you can see.
http://www.codeproject.com/search.aspx?q=com+using+C&doctypeid=1%3b2%3b3&authorid=88625
I was reading your thread here last night and it reminded me of similar reading at codeproject. So I went searching to find this link for all here.
Hi Nick!
Yes, Jeff Glatt's articles are wonderful. In this tutorial #1 I used C++, but in many ways one can't really understand the layout of COM objects unless one looks at it from the C perspective. In my tutorial #2 I used C I believe, and that translates more clearly to PowerBASIC. The situation becomes especially clearer when one looks at objects with multiple VTables/interfaces from the C perspective. I found one lack of Jeff's treatement of this to be that his 1st example created an object with just one VTable. When you have multiple VTables it becomes necessary to understand the mechanisms involved in switching between them. I have to admit I never looked at any of Jeff's tutorials beyond his 1st one.
Fred,
I used oleview to load a PowerBASIC created tlb.
I then saved the idl file. I then tried to create the c/c++ header files using MIDL.
I needed to edit the idl file to remove the .dll
All I got was another tlb file no ".h" file??
Can this be done?
James
Hello James!
If you use the midl compiler on something like test.idl you should get these files...
test.c
test.h
test.tlb
test_i.c
test_p.c
dlldata.c
dlldata.c and test_p.c is proxy/stub code for creating a marshaller dll. But you should get a test.h file with a lot of gunk in it. I'm not quite sure what you mean by this...
Quote
I needed to edit the idl file to remove the .dll
Fred,
Do I need some command line parameters? All midl is creating is another tlb file.
The file I loaded into oleview was PBCTES04.TLB
Here is the IDL file OleView created. I named it TLIB04.IDL
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>
[
uuid(7273541F-287F-4E32-92D4-4E25F501389E),
version(1.0),
helpstring("COM Library")
]
library PBCTEST04
{
// TLib : // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole32.tlb");
// Forward declare all types defined in this typelib
interface IDISPLAYOPENFILE;
[
odl,
uuid(B72CDD37-5349-46B7-8A27-592D23B34FE3),
helpstring("IDISPLAYOPENFILE is a custom interface for Direct VTable access."),
nonextensible
]
interface IDISPLAYOPENFILE : IUnknown {
[propput]
void _stdcall FOLDER([in] BSTR rhs);
[propput]
void _stdcall FILTER([in] BSTR rhs);
[propput]
void _stdcall HPARENT([in] unsigned long rhs);
[propput]
void _stdcall DFLEXT([in] BSTR rhs);
[propput]
void _stdcall START([in] BSTR rhs);
[propput]
void _stdcall TITLE([in] BSTR rhs);
[propput]
void _stdcall FLAGS([in] unsigned long rhs);
BSTR _stdcall GETNAME();
};
[
uuid(1B0F486F-9CCA-47BA-A338-8F7146B73C26)
]
coclass CDISPLAYOPENFILE {
[default] interface IDISPLAYOPENFILE;
};
};
This is copied from the terminal:
D:\ComTutorial1A\Server\PbSource>midl TLIB04.IDL
Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0555
Copyright (c) Microsoft Corporation. All rights reserved.
Processing .\TLIB04.IDL
TLIB04.IDL
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\oaidl.idl
oaidl.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\objidl.id
l
objidl.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\unknwn.id
l
unknwn.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\wtypes.id
l
wtypes.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\basetsd.h
basetsd.h
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\guiddef.h
guiddef.h
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\oaidl.acf
oaidl.acf
D:\ComTutorial1A\Server\PbSource>
No files anywhere I could find.
James
I believe you need to have interfaces declared outside of a IDL library statement if you want to have *.h and other
source code generated by MIDL.
Also, this from p148 of "Developer's Workshop To COM And ATL 3.0" by Andrew W. Troelsen...
Quote
When designing COM interfaces, we should always return HRESULTs as the physical return value of the method...
When I took the IDISPLAYOPENFILE interface out of the library structure, midl wouldn't compile it and complained
about the void return values you had. I imagine you are working on a COM wrapper for the Open File Dialog, which
of course returns a file name string, so I'd return a BSTR pointer as an [out, retval] as seen in what I have below.
This idl file correctly compiled for me, and the output is below...
import "oaidl.idl";
[object, uuid(B72CDD37-5349-46B7-8A27-592D23B34FE3), helpstring("IDISPLAYOPENFILE is a custom interface for Direct VTable access."), nonextensible]
interface IDISPLAYOPENFILE : IUnknown
{
HRESULT _stdcall FOLDER ([in] BSTR rhs );
HRESULT _stdcall FILTER ([in] BSTR rhs );
HRESULT _stdcall HPARENT ([in] unsigned long rhs );
HRESULT _stdcall DFLEXT ([in] BSTR rhs );
HRESULT _stdcall START ([in] BSTR rhs );
HRESULT _stdcall TITLE ([in] BSTR rhs );
HRESULT _stdcall FLAGS ([in] unsigned long rhs );
HRESULT _stdcall GETNAME ([out, retval] BSTR* strName );
};
[uuid(7273541F-287F-4E32-92D4-4E25F501389E), version(1.0), helpstring("COM Library")]
library PBCTEST04
{
importlib("stdole32.tlb");
// Forward declare all types defined in this typelib
interface IDISPLAYOPENFILE;
[uuid(1B0F486F-9CCA-47BA-A338-8F7146B73C26)]
coclass CDISPLAYOPENFILE
{
[default] interface IDISPLAYOPENFILE;
};
};
C:\Code\VStudio\VC++6\Projects\PBCTES04>midl pbctes04.idl
Microsoft (R) MIDL Compiler Version 5.01.0164
Copyright (c) Microsoft Corp 1991-1997. All rights reserved.
Processing .\pbctes04.idl
pbctes04.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\oaidl.idl
oaidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\objidl.idl
objidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\unknwn.idl
unknwn.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\wtypes.idl
wtypes.idl
C:\Code\VStudio\VC++6\Projects\PBCTES04>dir
Volume in drive C is Main
Volume Serial Number is 881C-5BB1
Directory of C:\Code\VStudio\VC++6\Projects\PBCTES04
05/17/2011 11:29a <DIR> .
05/17/2011 11:29a <DIR> ..
05/17/2011 11:29a 811 dlldata.c
05/17/2011 10:35a 74 PBCTES04.bat
05/17/2011 11:29a 9,571 pbctes04.h
05/17/2011 11:29a 962 PBCTES04.idl
05/17/2011 11:29a 2,192 pbctes04.tlb
05/17/2011 11:29a 1,128 pbctes04_i.c
05/17/2011 11:29a 37,104 pbctes04_p.c
8 File(s) 52,799 bytes
2 Dir(s) 48,632,020,992 bytes free
C:\Code\VStudio\VC++6\Projects\PBCTES04>
Fred,
Does it appear to be a problem with the PowerBASIC tlb ?
Create a simple PowerBASIC COM server and see what oleview returns.
James
I think another problem I'm having is using vs express 2010 and the win7 sdk which is 32/64. I've played around with /win32 for midl but I still get errorseven when I used midl on an IDL file I created with oleview from scrrun.
James
Fred,
Thanks for your input. I think I have a handle on it now. The void was because I was using IUnknown instead id IDispatch.
It also appears oleview does not produce an IDL in a format for midl to generate the source files. I've decided to parse the oleview idl file myself.
James
Give me a chance to look into it. Its been awhile since I've played around with OleView. Isn't there a flag there (setting) about creating ODL or IDL compliant output? I noticed there was an ODL attribute in your original *.idl.
Hi James!
Yes, I agree with you. I just spent some time playing with it, and saw that one gets adequate Interface definitions from OleView, but one must extract them from within the library clause to make a midl compilable *.idl file. Otherwise, midl just reproduces the typelib as it read it (and as you posted yesterday)!
Irregardless, its ( OleView ) certainly a really useful tool!
Fred
Fred,
Any examples (pb and c++) using IDispatch instead of IUnknown?
James
Just this (which you must have seen)....
http://www.jose.it-berater.org/smfforum/index.php?topic=4018.0
I believe it was last fall I spent like about a whole week trying desperately to use GNU mingw and Code::Blocks to create COM dlls but completely failed. I asked about it at Daniweb (big C++ site), but no one could help. Where it comes apart is with *.def files. They appear to be the only way to create COM dlls with C/C++, and mingw can't get them right.
I still use my VC 6 for most C/C++ fooling around with COM I do. I have VS 2008 pro too but far as I'm concerned its a pita.
How about a PowerBASIC DUAL com server and c++ vtable access code? Or how to approach it?
James
Fred,
I have working code using PowerBASIC dll server inherited from IUNKNOWN but not with DUAL.
My c++ is so poor it is probably on that end but I just don't know. It is probably the virtual class layout??
Note the c++ code was created with Bcx. It compiles with both MinGw and vs2010.
James
PowerBASIC IUNKNOWN server
#COMPILE DLL "PBTEST10_UNK.DLL"
#DIM ALL
#COM TLIB ON
#COM NAME "PBTEST10_UNK"
#COM GUID GUID$ ("{B3723C2A-7DCF-437A-AF59-42F19122B39B}")
#RESOURCE TYPELIB,1,"PBTEST10_UNK.TLB"
#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
$cPbTest10Guid = GUID$("{0D7051A6-456A-4631-9878-1B7FA5433539}")
$iPbTest10Guid = GUID$("{EAA6ED6B-B73D-44B6-832E-1B4BE67032D3}")
CLASS cPbTest10 $cPbTest10Guid AS COM
INSTANCE p1 AS LONG
CLASS METHOD CREATE
p1 = 77
STDOUT "Hello From the pbtest10_unk com server create method"
END METHOD
INTERFACE iPbTest10 $iPbTest10Guid : INHERIT IUNKNOWN
PropGet(p1,LONG)
PropSet(p1,LONG)
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
'==============================================================================
The c++ code that compiles and works with the above server
// *********************************************************************
// Created with BCX32 - BASIC To C/C++ Translator (V) 8.7.0.0 (2011/05/11)
// BCX (c) 1999 - 2009 by Kevin Diggins
// *********************************************************************
// Translated for compiling with a C++ Compiler
// *********************************************************************
// Additional lines may be needed
#if defined( __cplusplus )
using namespace std;
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
typedef std::string stdstr;
#endif
#include <windows.h> // Win32 Header File
#include <windowsx.h> // Win32 Header File
#include <commctrl.h> // Win32 Header File
#include <commdlg.h> // Win32 Header File
#include <mmsystem.h> // Win32 Header File
#include <shellapi.h> // Win32 Header File
#include <shlobj.h> // Win32 Header File
#include <richedit.h> // Win32 Header File
#include <wchar.h> // Win32 Header File
#include <objbase.h> // Win32 Header File
#include <ocidl.h> // Win32 Header File
#include <winuser.h> // Win32 Header File
#include <olectl.h> // Win32 Header File
#include <oaidl.h> // Win32 Header File
#include <ole2.h> // Win32 Header File
#include <oleauto.h> // Win32 Header File
#include <winsock.h> // Win32 Header File
#include <process.h> // dos
#include <conio.h> // dos
#include <direct.h> // dos
#include <io.h> // dos
#include <ctype.h> // dos/linux
#include <fcntl.h> // dos/linux
#include <math.h> // dos/linux
#include <stdio.h> // dos/linux
#include <string.h> // dos/linux
#include <stddef.h> // dos/linux
#include <stdlib.h> // dos/linux
#include <setjmp.h> // dos/linux
#include <time.h> // dos/linux
#include <stdarg.h> // dos/linux
// ***************************************************
// Compiler Defines
// ***************************************************
// C++
#if defined( __cplusplus )
#define overloaded
#define C_EXPORT EXTERN_C __declspec(dllexport)
#define C_IMPORT EXTERN_C __declspec(dllimport)
#else
#define C_EXPORT __declspec(dllexport)
#define C_IMPORT __declspec(dllimport)
#endif
// Open Watcom defs
#if defined( __WATCOM_CPLUSPLUS__ ) || defined( __TINYC__ )
#define atanl atan
#define sinl sin
#define cosl cos
#define tanl tan
#define asinl asin
#define acosl acos
#define log10l log10
#define logl log
#define _fcloseall fcloseall
#endif
// Borland C++ 5.5.1 defs - bcc32.exe
#if defined( __BCPLUSPLUS__ )
// ===== Borland Libraries ==========
#include <dos.h>
#pragma comment(lib,"import32.lib")
#pragma comment(lib,"cw32.lib")
// ==================================
#endif
// Microsoft VC++
#ifndef DECLSPEC_UUID
#if (_MSC_VER >= 1100) && defined ( __cplusplus )
#define DECLSPEC_UUID(x) __declspec(uuid(x))
#else
#define DECLSPEC_UUID(x)
#endif
#endif
// *************************************************
#ifndef __cplusplus
#error A C++ compiler is required
#endif
#if !defined( __LCC__ )
// *************************************************
// Instruct Linker to Search Object/Import Libraries
// *************************************************
#pragma comment(lib,"kernel32.lib")
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"gdi32.lib")
#pragma comment(lib,"comctl32.lib")
#pragma comment(lib,"advapi32.lib")
#pragma comment(lib,"winspool.lib")
#pragma comment(lib,"shell32.lib")
#pragma comment(lib,"ole32.lib")
#pragma comment(lib,"oleaut32.lib")
#pragma comment(lib,"uuid.lib")
#pragma comment(lib,"odbc32.lib")
#pragma comment(lib,"odbccp32.lib")
#pragma comment(lib,"winmm.lib")
#pragma comment(lib,"comdlg32.lib")
#pragma comment(lib,"imagehlp.lib")
#pragma comment(lib,"version.lib")
#else
#pragma lib <winspool.lib>
#pragma lib <shell32.lib>
#pragma lib <ole32.lib>
#pragma lib <oleaut32.lib>
#pragma lib <uuid.lib>
#pragma lib <odbc32.lib>
#pragma lib <odbccp32.lib>
#pragma lib <winmm.lib>
#pragma lib <imagehlp.lib>
#pragma lib <version.lib>
// *************************************************
// End of Object/Import Libraries To Search
// *************************************************
#endif
// *************************************************
// User Defined Constants
// *************************************************
#define cSizeOfDefaultString 2048
#define GetClassObj(dll) (HRESULT(__stdcall*)(REFCLSID,REFIID,void**))GetProcAddress(hDll,"DllGetClassObject")
// *************************************************
// Standard Prototypes
// *************************************************
void Pause (void);
// *************************************************
// User Defined Types And Unions
// *************************************************
class IPbTest10: public IUnknown
{
public:
virtual long __stdcall p1_get();
virtual void __stdcall p1_set(long);
};
// *************************************************
// User Global Variables
// *************************************************
// *************************************************
// User Prototypes
// *************************************************
int main (int,PCHAR*);
// *************************************************
// User Global Initialized Arrays
// *************************************************
static const CLSID CLSID_cPbTest10Guid={0x0D7051A6,0x456A,0x4631,{0x98,0x78,0x1B,0x7F,0xA5,0x43,0x35,0x39}};
static const IID IID_iPbTest10Guid={0xEAA6ED6B,0xB73D,0x44B6,{0x83,0x2E,0x1B,0x4B,0xE6,0x70,0x32,0xD3}};
// *************************************************
// Runtime Functions
// *************************************************
void Pause(void)
{
printf("\n%s\n","Press any key to continue . . .");
_getch();
}
HRESULT( __stdcall*ptrDllGetClassObject)(const CLSID&,const IID&,void**);
// ************************************
// User Subs and Functions
// ************************************
int main (int argc,PCHAR* argv)
{
IPbTest10* pPbt=NULL;
HRESULT hr;
IClassFactory* pCF=NULL;
HMODULE hDll=NULL;
BSTR str1;
long l;
static char stemp[cSizeOfDefaultString];
hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBTEST10_UNK.DLL");
if(!hDll )
{
MessageBox (GetActiveWindow(),"Bad Load","",0);
return 0;
}
ptrDllGetClassObject=GetClassObj(hDll);
hr=ptrDllGetClassObject(CLSID_cPbTest10Guid,IID_IClassFactory,(void**)&pCF);
if((SUCCEEDED(hr)))
{
pCF->CreateInstance(NULL,IID_iPbTest10Guid,(void**)&pPbt);
pCF->Release();
l=pPbt->p1_get();
cout<<" l = "<<l<<endl;
pPbt->p1_set(128);
l=pPbt->p1_get();
cout<<" l = "<<l<<endl;
hr=pPbt->Release();
}
if(hDll )
{
FreeLibrary(hDll);
}
Pause();
return 0;
}
The PowerBASIC DUAL code
#COMPILE DLL "PBTEST10_DUAL.DLL"
#DIM ALL
#COM TLIB ON
#COM NAME "PBTEST10_DUAL"
#COM GUID GUID$("{F86AA908-0A93-4F64-8E36-549A4850AA64}")
#RESOURCE TYPELIB,1,"PBTEST10_DUAL.TLB"
#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
$cPbTest10Guid = GUID$("{F455D1C8-37A2-40DD-901D-59D8EE7C33D2}")
$iPbTest10Guid = GUID$("{DDD0F77A-A178-4B88-A115-B80DD9FD7AED}")
CLASS cPbTest10 $cPbTest10Guid AS COM
INSTANCE p1 AS LONG
CLASS METHOD CREATE
p1 = 77
STDOUT "Hello From the pbtest10_dual com server create method"
END METHOD
INTERFACE iPbTest10 $iPbTest10Guid : INHERIT DUAL
'PropGet(p1,LONG)
'PropSet(p1,LONG)
METHOD p1_get() AS LONG
METHOD = p1
END METHOD
METHOD p1_set(p AS LONG)
p1 = p
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
'==============================================================================
and the c++ that fails.
// *********************************************************************
// Created with BCX32 - BASIC To C/C++ Translator (V) 8.7.0.0 (2011/05/11)
// BCX (c) 1999 - 2009 by Kevin Diggins
// *********************************************************************
// Translated for compiling with a C++ Compiler
// *********************************************************************
// Additional lines may be needed
#if defined( __cplusplus )
using namespace std;
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
typedef std::string stdstr;
#endif
#include <windows.h> // Win32 Header File
#include <windowsx.h> // Win32 Header File
#include <commctrl.h> // Win32 Header File
#include <commdlg.h> // Win32 Header File
#include <mmsystem.h> // Win32 Header File
#include <shellapi.h> // Win32 Header File
#include <shlobj.h> // Win32 Header File
#include <richedit.h> // Win32 Header File
#include <wchar.h> // Win32 Header File
#include <objbase.h> // Win32 Header File
#include <ocidl.h> // Win32 Header File
#include <winuser.h> // Win32 Header File
#include <olectl.h> // Win32 Header File
#include <oaidl.h> // Win32 Header File
#include <ole2.h> // Win32 Header File
#include <oleauto.h> // Win32 Header File
#include <winsock.h> // Win32 Header File
#include <process.h> // dos
#include <conio.h> // dos
#include <direct.h> // dos
#include <io.h> // dos
#include <ctype.h> // dos/linux
#include <fcntl.h> // dos/linux
#include <math.h> // dos/linux
#include <stdio.h> // dos/linux
#include <string.h> // dos/linux
#include <stddef.h> // dos/linux
#include <stdlib.h> // dos/linux
#include <setjmp.h> // dos/linux
#include <time.h> // dos/linux
#include <stdarg.h> // dos/linux
// ***************************************************
// Compiler Defines
// ***************************************************
// C++
#if defined( __cplusplus )
#define overloaded
#define C_EXPORT EXTERN_C __declspec(dllexport)
#define C_IMPORT EXTERN_C __declspec(dllimport)
#else
#define C_EXPORT __declspec(dllexport)
#define C_IMPORT __declspec(dllimport)
#endif
// Open Watcom defs
#if defined( __WATCOM_CPLUSPLUS__ ) || defined( __TINYC__ )
#define atanl atan
#define sinl sin
#define cosl cos
#define tanl tan
#define asinl asin
#define acosl acos
#define log10l log10
#define logl log
#define _fcloseall fcloseall
#endif
// Borland C++ 5.5.1 defs - bcc32.exe
#if defined( __BCPLUSPLUS__ )
// ===== Borland Libraries ==========
#include <dos.h>
#pragma comment(lib,"import32.lib")
#pragma comment(lib,"cw32.lib")
// ==================================
#endif
// Microsoft VC++
#ifndef DECLSPEC_UUID
#if (_MSC_VER >= 1100) && defined ( __cplusplus )
#define DECLSPEC_UUID(x) __declspec(uuid(x))
#else
#define DECLSPEC_UUID(x)
#endif
#endif
// *************************************************
#ifndef __cplusplus
#error A C++ compiler is required
#endif
#if !defined( __LCC__ )
// *************************************************
// Instruct Linker to Search Object/Import Libraries
// *************************************************
#pragma comment(lib,"kernel32.lib")
#pragma comment(lib,"user32.lib")
#pragma comment(lib,"gdi32.lib")
#pragma comment(lib,"comctl32.lib")
#pragma comment(lib,"advapi32.lib")
#pragma comment(lib,"winspool.lib")
#pragma comment(lib,"shell32.lib")
#pragma comment(lib,"ole32.lib")
#pragma comment(lib,"oleaut32.lib")
#pragma comment(lib,"uuid.lib")
#pragma comment(lib,"odbc32.lib")
#pragma comment(lib,"odbccp32.lib")
#pragma comment(lib,"winmm.lib")
#pragma comment(lib,"comdlg32.lib")
#pragma comment(lib,"imagehlp.lib")
#pragma comment(lib,"version.lib")
#else
#pragma lib <winspool.lib>
#pragma lib <shell32.lib>
#pragma lib <ole32.lib>
#pragma lib <oleaut32.lib>
#pragma lib <uuid.lib>
#pragma lib <odbc32.lib>
#pragma lib <odbccp32.lib>
#pragma lib <winmm.lib>
#pragma lib <imagehlp.lib>
#pragma lib <version.lib>
// *************************************************
// End of Object/Import Libraries To Search
// *************************************************
#endif
// *************************************************
// User Defined Constants
// *************************************************
#define cSizeOfDefaultString 2048
#define GetClassObj(dll) (HRESULT(__stdcall*)(REFCLSID,REFIID,void**))GetProcAddress(hDll,"DllGetClassObject")
// *************************************************
// Standard Prototypes
// *************************************************
char* BCX_TmpStr(size_t,size_t= 128,int= 1);
char* curdir (void);
void Pause (void);
// *************************************************
// User Defined Types And Unions
// *************************************************
class IPbTest10: public IDispatch
{
public:
virtual long __stdcall p1_get();
virtual void __stdcall p1_set(long);
};
// *************************************************
// System Variables
// *************************************************
// *************************************************
// User Global Variables
// *************************************************
// *************************************************
// User Prototypes
// *************************************************
int main (int,PCHAR*);
// *************************************************
// User Global Initialized Arrays
// *************************************************
static const CLSID CLSID_cPbTest10Guid={0xF455D1C8,0x37A2,0x40DD,{0x90,0x1D,0x59,0xD8,0xEE,0x7C,0x33,0xD2}};
static const IID IID_iPbTest10Guid={0xDDD0F77A,0xA178,0x4B88,{0xA1,0x15,0xB8,0x0D,0xD9,0xFD,0x7A,0xED}};
// *************************************************
// Runtime Functions
// *************************************************
#ifndef BCXTmpStrSize
#define BCXTmpStrSize 2048
#endif
char *BCX_TmpStr (size_t Bites,size_t iPad,int iAlloc)
{
static int StrCnt;
static char *StrFunc[BCXTmpStrSize];
StrCnt=(StrCnt + 1) & (BCXTmpStrSize-1);
if(StrFunc[StrCnt]) {free (StrFunc[StrCnt]); StrFunc[StrCnt] = NULL;}
#if defined BCX_MAX_VAR_SIZE
if(Bites*sizeof(char)>BCX_MAX_VAR_SIZE)
{
printf("Buffer Overflow caught in BCX_TmpStr - requested space of %d EXCEEDS %d\n",(int)(Bites*sizeof(char)),BCX_MAX_VAR_SIZE);
abort();
}
#endif
if(iAlloc) StrFunc[StrCnt]=(char*)calloc(Bites+128,sizeof(char));
return StrFunc[StrCnt];
}
char *curdir (void)
{
register char *strtmp = BCX_TmpStr(2048);
GetCurrentDirectory (1024,strtmp);
return strtmp;
}
void Pause(void)
{
printf("\n%s\n","Press any key to continue . . .");
_getch();
}
HRESULT( __stdcall*ptrDllGetClassObject)(const CLSID&,const IID&,void**);
// ************************************
// User Subs and Functions
// ************************************
int main (int argc,PCHAR* argv)
{
IPbTest10* pPbt=NULL;
HRESULT hr;
IClassFactory* pCF=NULL;
HMODULE hDll=NULL;
BSTR str1;
long l;
static char stemp[cSizeOfDefaultString];
strcpy(stemp,curdir());
hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBTEST10_DUAL.DLL");
if(!hDll )
{
MessageBox (GetActiveWindow(),"Bad Load","",0);
return 0;
}
ptrDllGetClassObject=GetClassObj(hDll);
hr=ptrDllGetClassObject(CLSID_cPbTest10Guid,IID_IClassFactory,(void**)&pCF);
if((SUCCEEDED(hr)))
{
pCF->CreateInstance(NULL,IID_iPbTest10Guid,(void**)&pPbt);
pCF->Release();
l=pPbt->p1_get();
cout<<" l = "<<l<<endl;
pPbt->p1_set(128);
l=pPbt->p1_get();
cout<<" l = "<<l<<endl;
}
if(hDll )
{
FreeLibrary(hDll);
}
Pause();
return 0;
}
I examined the dual tlb with oleview and there are both IDispatch with the HRESULT returns and the IUnknown but how to get the IUnknown???
Gimme a little time to familiarize myself with it. Actually, that Dual interface example I gave a link to above (COM Tutorial #4) seems to be what you are interested in. I didn't put too much in the way of comments in it like I did the others I see. From a C++ perspective using IDispatch in C++ clients isn't something done unless one is a glutton for punishennt. You have to declare arrays or Variants, load the variants with various required info, etc., etc. Painful. Once you go through with jumping all the hurdles though, it works fine.
I see you are just trying to use direct access in your C++ code that is failing though. Like I said, I'll look at it.
By the way, I do have a debug version of that COM #4 IDispatch code with piles of debug printf statements in it if you want it. Actually, that's how I code/debug. I either output mountains of debug statements to the console or a log file.
Fred,
I have no desire to connect using IDispatch. My understanding (?) is a dual interface has both IDispatch and vtable. I want to connect using c++ and vtable of an unregistered and/or a registered interface. My code shows loading an unregistered but maybe this is not possible with a dual interface??
Thanks for taking a look.
James
Fred,
I just took a look at the other code.
How does one return a value as in my IUnknown example ?
James
Fred,
From an Inside COM makefile I found the parameters for midl:
#################################################
#
# Proxy source files
#
Iface.h Server.tlb Proxy.c Guids.c DllData.c : Server.idl
midl /h Iface.h /iid Guids.c /proxy Proxy.c Server.idl
#################################################
James
Hi James!
Its kind of like I thought. Funny stuff goes on. I compiled the PBTEST10_DUAL.dll and registered it with RegSvr32 (didn't feel like playing with function pointers today). Here is a PB 6 program that accesses it...
'Console Compiler 6
#Compile Exe
#Dim All
$CLSID_CPBTEST10 = Guid$("{F455D1C8-37A2-40DD-901D-59D8EE7C33D2}")
$IID_IPBTEST10 = Guid$("{DDD0F77A-A178-4B88-A115-B80DD9FD7AED}")
' Interface Name : IPBTEST10
' Description : IPBTEST10 is a dual interface with VTable/Dispatch access.
' Class Name : CPBTEST10
' ClassID : $CLSID_CPBTEST10
Interface IPBTEST10 $IID_IPBTEST10 : Inherit IDispatch
Method P1_GET <257> () As Long
Method P1_SET <258> (ByRef InOut P As Long)
End Interface
Function PBMain() As Long
Local pIPbTest10 As IPBTEST10
pIPbTest10=NewCom(ProgId$($CLSID_CPBTEST10))
If IsObject(pIPbTest10) Then
Print "pWhatever Is An Object!"
pIPbTest10.p1_Set(25)
Print "pIPbTest10.p1_Get() = " pIPbTest10.p1_Get()
Set pIPbTest10=Nothing
Else
Print "pWhatever Ain't Crap!"
End If
Con.WaitKey$
PBMain=0
End Function
'Hello From the pbtest10_dual com server create method
'pWhatever Is An Object!
'pIPbTest10.p1_Get() = 25
...and here is a C++ program that does the same (direct access)...
#include <windows.h>
#include <stdio.h>
const CLSID CLSID_CPBTEST10 = {0xF455D1C8,0x37A2,0x40DD,{0x90,0x1D,0x59,0xD8,0xEE,0x7C,0x33,0xD2}};
const IID IID_IPBTEST10 = {0xDDD0F77A,0xA178,0x4B88,{0xA1,0x15,0xB8,0x0D,0xD9,0xFD,0x7A,0xED}};
interface IPBTest : IDispatch
{
virtual HRESULT __stdcall p1_Get(int* pInt);
virtual HRESULT __stdcall p1_Set(int* pNum);
};
int main(void)
{
IPBTest* pTest=NULL;
int iNumber=0;
HRESULT hr;
OleInitialize(NULL);
hr=CoCreateInstance(CLSID_CPBTEST10,NULL,CLSCTX_INPROC_SERVER,IID_IPBTEST10,(void**)&pTest);
if(SUCCEEDED(hr))
{
printf("CoCreateInstance() Succeeded!\n");
hr=pTest->p1_Get(&iNumber);
if(SUCCEEDED(hr))
printf("iNumber = %d\n",iNumber);
pTest->Release();
}
OleUninitialize();
getchar();
return 0;
}
/*
Hello From the pbtest10_dual com server create method
CoCreateInstance() Succeeded!
iNumber = 77
*/
Note the funny business with the hidden HRESULT between the PB and C code.
If you redo the prototype for the 'get' to pass in a pointer for a [retval] I'm thinking it'll work.
Since there aren't too many parameters IDispatch wouldn't be bad.
Of course, OleView shows the 'hidden' parameter.
Fred,
Thank you very much.
Now how about IDictionary from scrrun.dll from vtable c++ :)
James