....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......