IT-Consultant: Frederick J. Harris > Fred's COM (Component Object Model) Tutorials
COM Tutorial #1: COM Object Memory
Frederick J. Harris:
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….
--- Code: ---‘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()
--- End code ---
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….
--- Code: ---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.
--- End code ---
Now for the files…
Server.cpp
--- Code: ---//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
--- End code ---
CA.cpp
--- Code: ---//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
--- End code ---
Ifunctions.h
--- Code: ---//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++…
--- End code ---
--- Code: ---//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
--- End code ---
Registry.h
--- Code: ---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);
--- End code ---
Registry.cpp
--- Code: ---// 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
--- End code ---
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.
--- Code: ---;//CA.def
LIBRARY "CA"
DESCRIPTION "CA Windows Dynamic Link Library"
EXPORTS
DllGetClassObject PRIVATE
DllCanUnloadNow PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
--- End code ---
Frederick J. Harris:
....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:
--- Code: ---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.
--- End code ---
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++…
--- Code: ---#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()
--- End code ---
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…
--- Code: ---#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()
--- End code ---
And finally, here is an exact translation of the above into C++…
--- Code: ---#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()
*/
--- End code ---
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…
--- Code: --- Type SomeType
A As Integer
B As Integer
C As Integer
End Type
--- End code ---
And then another type…
--- Code: --- Type Another
BasicInterface As SomeType
D As Integer
E As Integer
End Type
--- End code ---
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
--- Code: ---
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
--- End code ---
Then C++ from CA.h
--- Code: ---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;
};
--- End code ---
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.,
--- Code: ---PowerBASIC: Interface I_X : Inherit IUnknown
C++: interface I_X : IUnknown
--- End code ---
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…
--- Code: ---Type IUnknown
QueryInterface As Dword Ptr
AddRef As Dword Ptr
Release As Dword Ptr
End Type
--- End code ---
….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…
--- Code: ---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
--- End code ---
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…
--- Code: ---CA::CA()
{
m_lRef=0;
InterlockedIncrement(&g_lObjs);
}
--- End code ---
We’ll alter that as follows, recompile, then run the very same PowerBASIC program above…
--- Code: ---CA::CA()
{
m_lRef=0;
InterlockedIncrement(&g_lObjs);
printf("sizeof(CA) = %u\n",sizeof(CA));
printf("this = %u\n",this);
}
--- End code ---
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......
Frederick J. Harris:
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.
--- Code: ---#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()
*/
--- End code ---
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!
--- Code: ---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
);
--- End code ---
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.
--- Code: ---#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()
--- End code ---
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++…
--- Code: ---#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()
--- End code ---
//C++ Version
--- Code: ---#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()
*/
--- End code ---
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…
--- Code: ---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;
}
--- End code ---
(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).
Frederick J. Harris:
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).
James C. Fuller:
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
Navigation
[0] Message Index
[#] Next page
Go to full version