COM in Plain C - CodeProject
COM in Plain C - CodeProject
COMinplainCCodeProject
articles
Q&A
forums
Sign in
lounge
Searchforarticles,questions,tips
COM in plain C
Jeff Glatt, 28 Mar 2006
CPOL
Rate this:
Contents
A COM object and its VTable
A GUID
QueryInterface, AddRef, and Release
An IClassFactory object
Packaging into a DLL
Our C++/C include file
The Definition DEF file
Install the DLL, and register the object
An example C program
An example C++ program
Modifying the code
What's next?
Introduction
There are numerous examples that demonstrate how to use/create COM/OLE/ActiveX components. But these examples typically
use Microsoft Foundation Classes MFC, .NET, C#, WTL, or at least ATL, because those frameworks have prefabricated
"wrappers" to give you some boilerplate code. Unfortunately, these frameworks tend to hide all of the low level details from a
programmer, so you never really do learn how to use COM components per se. Rather, you learn how to use a particular
framework riding on top of COM.
If you're trying to use plain C, without MFC, WTL, .NET, ATL, C#, or even any C++ code at all, then there is a dearth of examples
and information on how to deal with COM objects. This is the first in a series of articles that will examine how to utilize COM in
plain C, without any frameworks.
With standard Win32 controls such as a Static, Edit, Listbox, Combobox, etc., you obtain a handle to the control i.e., an HWND
and pass messages via SendMessage to it in order to manipulate it. Also, the control passes messages back to you i.e., by
putting them in your own message queue, and you fetch them with GetMessage when it wants to inform you of something
http://www.codeproject.com/Articles/13601/COMinplainC
1/23
1/21/2016
COMinplainCCodeProject
structIExample{
DWORDcount;
charbuffer[80];
};
Let's use a typedef to make it easier to work with:
Hide Copy Code
typedefstruct{
DWORDcount;
charbuffer[80];
}IExample;
And here's an example of allocating an instance of the above struct error checking omitted, and initializing its members:
Hide Copy Code
IExample*example;
example=(IExample*)GlobalAlloc(GMEM_FIXED,sizeof(IExample));
example>count=1;
example>buffer[0]=0;
Did you know that a struct can store a pointer to some function? Hopefully, you did, but here's an example. Let's say we have a
function which is passed a char pointer, and returns a long. Here's our function:
Hide Copy Code
longSetString(char*str)
http://www.codeproject.com/Articles/13601/COMinplainC
2/23
1/21/2016
COMinplainCCodeProject
{
return(0);
}
Now we want to store a pointer to this function inside IExample. Here's how we define IExample, adding a member
"SetString" to store a pointer to the above function and I'll use a typedef to make this more readable:
Hide Copy Code
typedeflongSetStringPtr(char*);
typedefstruct{
SetStringPtr*SetString;
DWORDcount;
charbuffer[80];
}IExample;
And here's how we store a pointer to SetString inside our allocated IExample, and then call SetString using that
pointer:
Hide Copy Code
example>SetString=SetString;
longvalue=example>SetString("Sometext");
OK, maybe we want to store pointers to two functions. Here's a second function:
Hide Copy Code
longGetString(char*buffer,longlength)
{
return(0);
}
Let's redefine IExample, adding another member "GetString" to store a pointer to this second function:
Hide Copy Code
typedeflongGetStringPtr(char*,long);
typedefstruct{
SetStringPtr*SetString;
GetStringPtr*GetString;
DWORDcount;
charbuffer[80];
}IExample;
And here we initialize this member:
Hide Copy Code
example>GetString=GetString;
But let's say we don't want to store the function pointers directly inside of IExample. Instead, we'd rather have an array of
function pointers. For example, let's define a second struct whose sole purpose is to store our two function pointers. We'll call
this a IExampleVtbl struct, and define it as so:
Hide Copy Code
typedefstruct{
SetStringPtr*SetString;
GetStringPtr*GetString;
}IExampleVtbl;
http://www.codeproject.com/Articles/13601/COMinplainC
3/23
1/21/2016
COMinplainCCodeProject
Now, we'll store a pointer to the above array inside of IExample. We'll add a new member called "lpVtbl" for that purpose
and of course, we'll remove the SetString and GetString members since they've been moved to the IExampleVtbl
struct:
Hide Copy Code
typedefstruct{
IExampleVtbl*lpVtbl;
DWORDcount;
charbuffer[80];
}IExample;
So here's an example of allocating and initializing a IExample and of course, a IExampleVtbl:
Hide Copy Code
//SincethecontentsofIExample_Vtblwillneverchange,we'll
//justdeclareitstaticandinitializeitthatway.Itcan
//bereusedforlotsofinstancesofIExample.
staticconstIExampleVtblIExample_Vtbl={SetString,GetString};
IExample*example;
//Create(allocate)aIExamplestruct.
example=(IExample*)GlobalAlloc(GMEM_FIXED,sizeof(IExample));
//InitializetheIExample(ie,storeapointerto
//IExample_Vtblinit).
example>lpVtbl=&IExample_Vtbl;
example>count=1;
example>buffer[0]=0;
And to call our functions, we do:
Hide Copy Code
charbuffer[80];
example>lpVtbl>SetString("Sometext");
example>lpVtbl>GetString(buffer,sizeof(buffer));
One more thing. Let's say we've decided that our functions may need to access the "count" and "buffer" members of the
struct used to call them. So, what we'll do is always pass a pointer to that struct as the first argument. Let's rewrite our functions
to accommodate this:
Hide Shrink
Copy Code
typedeflongSetStringPtr(IExample*,char*);
typedeflongGetStringPtr(IExample*,char*,long);
longSetString(IExample*this,char*str)
{
DWORDi;
//Let'scopythepassedstrtoIExample'sbuffer
i=lstrlen(str);
if(i>79)i=79;
CopyMemory(this>buffer,str,i);
this>buffer[i]=0;
return(0);
}
longGetString(IExample*this,char*buffer,longlength)
{
http://www.codeproject.com/Articles/13601/COMinplainC
4/23
1/21/2016
COMinplainCCodeProject
DWORDi;
//Let'scopyIExample'sbuffertothepassedbuffer
i=lstrlen(this>buffer);
length;
if(i>length)i=length;
CopyMemory(buffer,this>buffer,i);
buffer[i]=0;
return(0);
}
And let's pass a pointer to the IExample struct when calling its functions:
Hide Copy Code
example>lpVtbl>SetString(example,"Sometext");
example>lpVtbl>GetString(example,buffer,sizeof(buffer));
If you've ever used C++, you may be thinking "Wait a minute. This seems strangely familiar." It should. What we've done above
is to recreate a C++ class, using plain C. The IExample struct is really a C++ class one that doesn't inherit from any other
class. A C++ class is really nothing more than a struct whose first member is always a pointer to an array an array that
contains pointers to all the functions inside of that class. And the first argument passed to each function is always a pointer to
the class i.e., struct itself. This is referred to as the hidden "this" pointer.
At its simplest, a COM object is really just a C++ class. You're thinking "Wow! IExample is now a COM object? That's all there
is to it?? That was easy!" Hold on. IExample is getting closer, but there's much more to it. It's not that easy. If it were, this
wouldn't be a "Microsoft technology", now would it?
First of all, let's introduce some COM technobabble. You see that array of pointers above the IExampleVtbl struct? COM
documentation refers to that as an interface or VTable.
One requirement of a COM object is that the first three members of our VTable i.e., our IExampleVtbl struct must be
called QueryInterface, AddRef, and Release. And of course, we have to write those three functions. Microsoft has
already determined what arguments must be passed to these functions, what they must return, and what calling convention
they use. We'll need to #include some Microsoft include files that either ship with your C compiler, or you download the
Microsoft SDK. We'll redefine our IExampleVtbl struct as so:
Hide Copy Code
#include<windows.h>
#include<objbase.h>
#include<INITGUID.H>
typedefHRESULTSTDMETHODCALLTYPEQueryInterfacePtr(IExample*,REFIID,void**);
typedefULONGSTDMETHODCALLTYPEAddRefPtr(IExample*);
typedefULONGSTDMETHODCALLTYPEReleasePtr(IExample*);
typedefstruct{
//First3membersmustbecalledQueryInterface,AddRef,andRelease
QueryInterfacePtr*QueryInterface;
AddRefPtr*AddRef;
ReleasePtr*Release;
SetStringPtr*SetString;
GetStringPtr*GetString;
}IExampleVtbl;
Let's examine that typedef for QueryInterface. First of all, the function returns an HRESULT. This is defined simply as a
long. Next, it uses STDMETHODCALLTYPE. This means that arguments are not passed in registers, but rather, on the stack.
And this also determines who does cleanup of the stack. In fact, for a COM object, we should make sure that all of our functions
are declared with STDMETHODCALLTYPE, and return a long HRESULT. The first argument passed to
QueryInterface is a pointer to the object used to call the function. Aren't we turning IExample into a COM object? Yes,
http://www.codeproject.com/Articles/13601/COMinplainC
5/23
1/21/2016
COMinplainCCodeProject
and that's what we're going to pass for this argument. Remember we decided that the first argument we pass to any of our
functions will be a pointer to the struct used to call that function? COM is simply enforcing, and relying upon, this design.
Later, we'll examine what a REFIID is, and also talk about what that third argument to QueryInterface is for. But for
now, note that AddRef and Release also are passed that same pointer to our struct we use to call them.
OK, before we forget, let's add HRESULTSTDMETHODCALLTYPE to SetString and GetString:
Hide Copy Code
typedefHRESULTSTDMETHODCALLTYPESetStringPtr(IExample*,char*);
typedefHRESULTSTDMETHODCALLTYPEGetStringPtr(IExample*,char*,long);
HRESULTSTDMETHODCALLTYPESetString(IExample*this,char*str)
{
...
return(0);
}
HRESULTSTDMETHODCALLTYPEGetString(IExample*this,char*buffer,longvalue)
{
...
return(0);
}
In conclusion, a COM object is basically a C++ class. A C++ class is just a struct that always starts with a pointer
to its VTable an array of function pointers. And the first three pointers in the VTable will always be named
QueryInterface, AddRef, and Release. What additional functions may be in its VTable, and what the
name of their pointers are, depends upon what type of object it is. You determine what other functions you
want to add to your COM object. For example, Internet Explorer's browser object will undoubtedly have different
functions than some object that plays music. But all COM objects begin with a pointer to their VTable, and the
first three VTable pointers are to the object's QueryInterface, AddRef, and Release functions. The first
argument passed to an object's function is a pointer to the object struct itself. That is the law. Obey it.
A GUID
Let's continue on our journey to make IExample a real COM object. We have yet to actually write our QueryInterface,
AddRef, and Release functions. But before we can do that, we must talk about something called a Globally Universal
Identifier GUID. Ack. What's that? It's a 16 byte array that is filled in with a unique series of bytes. And when I say unique, I do
mean unique. One GUID i.e., 16 byte array cannot have the same series of bytes as another GUID... anywhere in the world.
Every GUID ever created has a unique series of 16 bytes.
And how do you create that series of 16 unique bytes? You use a Microsoft utility called GUIDGEN.EXE. It either ships with your
compiler, or you get it with the SDK. Run it and you see this window:
http://www.codeproject.com/Articles/13601/COMinplainC
6/23
1/21/2016
COMinplainCCodeProject
As soon as you run GUIDGEN, it automatically generates a new GUID for you, and displays it in the Result box. Note that what
you see in your Result box will be different than the above. After all, every single GUID generated will be different than any
other. So you had better be seeing something different than I see. Go ahead and click on the "New GUID" button to see some
different numbers appear in the Result box. Click all day and entertain yourself by seeing if you ever generate the same series of
numbers more than once. You won't. And what's more, nobody else will ever generate any of those number series you
generate.
You can click on the "Copy" button to transfer the text to the clipboard, and paste it somewhere else like in your source code.
Here is what I pasted when I did that:
Hide Copy Code
//{0B5B3D8E574C4fa3901025B8E4CE24C2}
DEFINE_GUID(<<name>>
The above is a macro. A #define in one of the Microsoft include files allows your compiler to compile the above into a 16
byte array.
But there is one thing that we must do. We must replace <<name>> with some C variable name we want to use for this GUID.
Let's call it CLSID_IExample.
Hide Copy Code
//{0B5B3D8E574C4fa3901025B8E4CE24C2}
DEFINE_GUID(CLSID_IExample,0xb5b3d8e,0x574c,0x4fa3,
0x90,0x10,0x25,0xb8,0xe4,0xce,0x24,0xc2);
Now we have a GUID we can use with IExample.
We also need a GUID for IExample's VTable "interface", i.e., our IExampleVtbl struct. So go ahead and click on
GUIDGEN.EXE's New GUID button, and copy/paste it somewhere. This time, we're going to replace <<name>> with the C
variable name IID_IExample. Here's what I pasted/edited:
Hide Copy Code
//{74666CACC2B14fa8A04997F3214802F0}
DEFINE_GUID(IID_IExample,0x74666cac,0xc2b1,0x4fa8,
0xa0,0x49,0x97,0xf3,0x21,0x48,0x2,0xf0);
http://www.codeproject.com/Articles/13601/COMinplainC
7/23
1/21/2016
COMinplainCCodeProject
In conclusion, every COM object has its own GUID, which is an array of 16 bytes that are different from any
other GUID. A GUID is created with the GUIDGEN.EXE utility. A COM object's VTable i.e., interface also has a
GUID.
Copy Code
HRESULTSTDMETHODCALLTYPEQueryInterface(IExample*this,
REFIIDvTableGuid,void**ppv)
{
//CheckiftheGUIDmatchesIExample
//VTable'sGUID.Rememberthatwegavethe
//CvariablenameIID_IExampletoour
//VTableGUID.WecanuseanOLEfunctioncalled
//IsEqualIIDtodothecomparisonforus.
if(!IsEqualIID(riid,&IID_IExample))
{
//Wedon'trecognizetheGUIDpassed
//tous.Letthecallerknowthis,
//byclearinghishandle,
//andreturningE_NOINTERFACE.
*ppv=0;
return(E_NOINTERFACE);
}
//It'samatch!
//First,wefillinhishandlewith
//thesameobjectpointerhepassedus.That's
//ourIExamplewecreated/initialized,
//andheobtainedfromus.
http://www.codeproject.com/Articles/13601/COMinplainC
8/23
1/21/2016
COMinplainCCodeProject
*ppv=this;
//NowwecallourownAddReffunction,
//passingtheIExample.
this>lpVtbl>AddRef(this);
//LethimknowheindeedhasaIExample.
return(NOERROR);
}
Now let's talk about our AddRef and Release functions. You'll notice we called AddRef in QueryInterface... if we
really did have a IExample.
Remember that we're allocating the IExample on behalf of the other program. He's simply gaining access to it. And it's our
responsibility to free it when the other program is done using it. How do we know when that is?
We're going to use something called "reference counting". If you look back at the definition of IExample, you'll see that I put
a DWORD member in there count. We're going to make use of this member. When we create a IExample, we'll initialize it
to 0. Then, we're going to increment this member by 1 every time AddRef is called, and decrement it by 1 every time
Release is called.
So, when our IExample is passed to QueryInterface, we call AddRef to increment its count member. When the
other program is done using it, the program will pass our IExample to our Release function, where we will decrement that
member. And if it's 0, we'll free IExample then.
This is another important rule of COM. If you get hold of a COM object created by someone else, you must call its Release
function when you're done with it. We certainly expect the other program to call our Release function when it is done with
our IExample object.
Here then are our AddRef and Release functions:
Hide Copy Code
ULONGSTDMETHODCALLTYPEAddRef(IExample*this)
{
//Incrementthereferencecount(countmember).
++this>count;
//We'resupposedtoreturntheupdatedcount.
return(this>count);
}
ULONGSTDMETHODCALLTYPERelease(IExample*this)
{
//Decrementthereferencecount.
this>count;
//Ifit'snowzero,wecanfreeIExample.
if(this>count==0)
{
GlobalFree(this);
return(0);
}
//We'resupposedtoreturntheupdatedcount.
return(this>count);
}
There's one more thing we're going to do. Microsoft has defined a COM object known as an IUnknown. What's that? An
IUnknown object is just like IExample, except its VTable contains only the QueryInterface, AddRef, and Release
functions i.e., it doesn't contain additional functions like our IExample VTable has SetString and GetString. In other
words, an IUnknown is the bare minimum COM object. And Microsoft created a special GUID for an IUnknown object. But
http://www.codeproject.com/Articles/13601/COMinplainC
9/23
1/21/2016
COMinplainCCodeProject
you know what? Our IExample object can also masquerade as an IUnknown object. After all, it has the
QueryInterface, AddRef, and Release functions in it. Nobody needs to know it's really an IExample object if all
they care about are just those first three functions. We're going to change one line of code so that we report success if the other
program passes us either our IExample GUID or an IUnknown GUID. And by the way, Microsoft's include files give the
IUnknown GUID the C variable name IID_IUnknown:
Hide Copy Code
//CheckiftheGUIDmatchesIExample'sGUIDorIUnknown'sGUID.
if(!IsEqualIID(vTableGuid,&IID_IExample)&&
!IsEqualIID(vTableGuid,&IID_IUnknown))
In conclusion, for our own COM object, we allocate it on behalf of some other program which gains access to
the object and uses it to call our functions. We're responsible for freeing the object. We use reference counting
in conjunction with our AddRef and Release functions to accomplish this safely. Our QueryInterface
allows other programs to verify they have the object they want, and also allows us to increment the reference
count. Actually, the QueryInterface primarily serves a different purpose that we'll examine later. But at this
point, it will suffice to think of its purpose this way.
So, is IExample now a real COM object? Yes it is! Great! Not too hard! We're done!
Wrong! We still have to package this thing into a form that another program can use i.e., a Dynamic Link Library, and write
code to do a special install routine, and examine how the other program gets hold of our IExample we create and that will
involve us writing more code.
An IClassFactory object
Now we need to look at how a program gets hold of one of our IExample objects, and ultimately, we have to write more
code to realize this. Microsoft has devised a standardized method for this. It involves us putting a second COM object and its
functions inside our DLL. This COM object is called an IClassFactory, and it has a specific set of functions already defined
in Microsoft's include files. It also has its own GUID already defined, and given the C variable name of IID_IClassFactory.
Our IClassFactory's VTable has five specific functions in it, which are QueryInterface, AddRef, Release,
CreateInstance, and LockServer. Notice that the IClassFactory has its own QueryInterface, AddRef, and
Release functions, just like our IExample object. After all, our IClassFactory is a COM object too, and the VTable of
all COM objects must start with those three functions. But to avoid a name conflict with IExample's functions, we'll preface
our IClassFactory's function names with "class", such as classQueryInterface, classAddRef, and
classRelease. As long as IClassFactory's VTable defines its first three members as QueryInterface, AddRef,
and Release, that's OK.
The really important function is CreateInstance. The program calls our IClassFactory's CreateInstance
whenever the program wants us to create one of our IExample objects, initialize it, and return it. In fact, if the program wants
several of our IExample objects, it can call CreateInstance numerous times. OK, so that's how a program gets hold of
one of our IExample objects. "But how does the program get hold of our IClassFactory object?", you may ask. We'll get
to that later. For now, let's simply write our IClassFactory's five functions, and make its VTable.
Making the VTable is easy. Unlike our IExample object's IExampleVtbl, we don't have to define our IClassFactory's
VTable struct. Microsoft has already done that for us by defining a IClassFactoryVtbl struct in some include file. All we
need to do is declare our VTable and fill it in with pointers to our five IClassFactory functions. Let's create a static VTable
using the variable name IClassFactory_Vtbl, and fill it in:
Hide Copy Code
staticconstIClassFactoryVtblIClassFactory_Vtbl={classQueryInterface,
classAddRef,
classRelease,
classCreateInstance,
http://www.codeproject.com/Articles/13601/COMinplainC
10/23
1/21/2016
COMinplainCCodeProject
classLockServer};
Likewise, creating an actual IClassFactory object is easy because Microsoft has already defined that struct too. We need
only one of them, so let's declare a static IClassFactory using the variable name MyIClassFactoryObj, and initialize
its lpVtbl member to point to our above VTable:
Hide Copy Code
staticIClassFactoryMyIClassFactoryObj={&IClassFactory_Vtbl};
Now, we just need to write those above five functions. Our classAddRef and classRelease functions are trivial. Because
we never actually allocate our IClassFactory i.e., we simple declare it as a static, we don't need to free anything. So,
classAddRef will simply return a 1 to indicate that there is always one IClassFactory hanging around. And
classRelease will do likewise. We don't need to do any reference counting for our IClassFactory since we don't have
to free it.
Hide Copy Code
ULONGSTDMETHODCALLTYPEclassAddRef(IClassFactory*this)
{
return(1);
}
ULONGSTDMETHODCALLTYPEclassRelease(IClassFactory*this)
{
return(1);
}
Now, let's look at our QueryInterface. It needs to check if the GUID passed to it is either an IUnknown's GUID since our
IClassFactory has the QueryInterface, AddRef, and Release functions, it too can masquerade as an IUnknown
object or an IClassFactory's GUID. Otherwise, we do the same thing as we did in IExample's QueryInterface.
Hide Copy Code
HRESULTSTDMETHODCALLTYPEclassQueryInterface(IClassFactory*this,
REFIIDfactoryGuid,void**ppv)
{
//CheckiftheGUIDmatchesanIClassFactoryorIUnknownGUID.
if(!IsEqualIID(factoryGuid,&IID_IUnknown)&&
!IsEqualIID(factoryGuid,&IID_IClassFactory))
{
//Itdoesn't.Clearhishandle,andreturnE_NOINTERFACE.
*ppv=0;
return(E_NOINTERFACE);
}
//It'samatch!
//First,wefillinhishandlewiththesameobjectpointerhepassedus.
//That'sourIClassFactory(MyIClassFactoryObj)heobtainedfromus.
*ppv=this;
//CallourIClassFactory'sAddRef,passingtheIClassFactory.
this>lpVtbl>AddRef(this);
//LethimknowheindeedhasanIClassFactory.
return(NOERROR);
}
Our IClassFactory's LockServer can be just a stub for now:
Hide Copy Code
HRESULTSTDMETHODCALLTYPEclassLockServer(IClassFactory*this,BOOLflock)
http://www.codeproject.com/Articles/13601/COMinplainC
11/23
1/21/2016
COMinplainCCodeProject
{
return(NOERROR);
}
There's one more function to write CreateInstance. This is defined as follows:
Hide Copy Code
HRESULTSTDMETHODCALLTYPEclassCreateInstance(IClassFactory*,
IUnknown*,REFIID,void**);
As usual, the first argument is going to be a pointer to our IClassFactory object MyIClassFactoryObj which was
used to call CreateInstance.
We use the second argument only if we implement something called aggregation. We won't get into this now. If this is non
zero, then someone wants us to support aggregation, which we're not going to do, and we will indicate that by returning an
error.
The third argument will be the IExample VTable's GUID if someone indeed wants us to allocate, initialize, and return a
IExample object.
The fourth argument is a handle where we'll return the IExample object we create.
So let's dive into our CreateInstance function named classCreateInstance:
Hide Shrink
Copy Code
HRESULTSTDMETHODCALLTYPEclassCreateInstance(IClassFactory*this,
IUnknown*punkOuter,REFIIDvTableGuid,void**ppv)
{
HRESULThr;
structIExample*thisobj;
//Assumeanerrorbyclearingcaller'shandle.
*ppv=0;
//Wedon'tsupportaggregationinIExample.
if(punkOuter)
hr=CLASS_E_NOAGGREGATION;
else
{
//CreateourIExampleobject,andinitializeit.
if(!(thisobj=GlobalAlloc(GMEM_FIXED,
sizeof(structIExample))))
hr=E_OUTOFMEMORY;
else
{
//StoreIExample'sVTable.Wedeclaredit
//asastaticvariableIExample_Vtbl.
thisobj>lpVtbl=&IExample_Vtbl;
//Incrementreferencecountsowe
//cancallRelease()belowanditwill
//deallocateonlyifthere
//isanerrorwithQueryInterface().
thisobj>count=1;
//Fillinthecaller'shandle
//withapointertotheIExamplewejust
//allocatedabove.We'llletIExample's
//QueryInterfacedothat,because
//italsocheckstheGUIDthecaller
//passed,andalsoincrementsthe
//referencecount(to2)ifallgoeswell.
http://www.codeproject.com/Articles/13601/COMinplainC
12/23
1/21/2016
COMinplainCCodeProject
hr=IExample_Vtbl.QueryInterface(thisobj,vTableGuid,ppv);
//Decrementreferencecount.
//NOTE:IftherewasanerrorinQueryInterface()
//thenRelease()willbedecrementing
//thecountbackto0andwillfreethe
//IExampleforus.Oneerrorthatmay
//occuristhatthecallerisaskingfor
//somesortofobjectthatwedon't
//support(ie,it'saGUIDwedon'trecognize).
IExample_Vtbl.Release(thisobj);
}
}
return(hr);
}
That takes care of implementing our IClassFactory object.
HRESULTPASCALDllGetClassObject(REFCLSIDobjGuid,
REFIIDfactoryGuid,void**factoryHandle);
The first argument passed is going to be the GUID for our IExample object not its VTable's GUID. We need to check this to
make sure that the caller definitely intended to call our DLL's DllGetClassObject. Note that every COM DLL has a
DllGetClassObject function in it, so again, we need that GUID to distinguish our DllGetClassObject from every
other COM DLL's DllGetClassObject.
The second argument is going to be the GUID of an IClassFactory.
The third argument is a handle to where the program expects us to return a pointer to our IClassFactory if the program
did indeed pass IExample's GUID, and not some other COM object's GUID.
Hide Shrink
Copy Code
HRESULTPASCALDllGetClassObject(REFCLSIDobjGuid,
REFIIDfactoryGuid,void**factoryHandle)
{
HRESULThr;
//Checkthatthecallerispassing
//ourIExampleGUID.That'stheCOM
//objectourDLLimplements.
http://www.codeproject.com/Articles/13601/COMinplainC
13/23
1/21/2016
COMinplainCCodeProject
if(IsEqualCLSID(objGuid,&CLSID_IExample))
{
//Fillinthecaller'shandle
//withapointertoourIClassFactoryobject.
//We'llletourIClassFactory's
//QueryInterfacedothat,becauseitalso
//checkstheIClassFactoryGUIDanddoesotherbookkeeping.
hr=classQueryInterface(&MyIClassFactoryObj,
factoryGuid,factoryHandle);
}
else
{
//Wedon'tunderstandthisGUID.
//It'sobviouslynotforourDLL.
//Letthecallerknowthisby
//clearinghishandleandreturning
//CLASS_E_CLASSNOTAVAILABLE.
*factoryHandle=0;
hr=CLASS_E_CLASSNOTAVAILABLE;
}
return(hr);
}
We're almost done with what we need to create our DLL. There's just one more thing. It's not really the program that loads our
DLL. Rather, the operating system does so on behalf of the program when the program calls CoGetDllClassObject i.e.,
CoGetClassObject locates our DLL file, does a LoadLibrary on it, uses GetProcAddress to get our above
DllGetClassObject, and calls it on behalf of the program. And unfortunately, Microsoft didn't work out any way for the
program to tell the OS when the program is done using our DLL and the OS should unload FreeLibrary our DLL. So we
have to help out the OS to let it know when it is safe to unload our DLL. We must provide a function called
DllCanUnloadNow which will return S_OK if it's safe to unload our DLL, or S_FALSE if not.
And how will we know when it is safe?
We're going to have to do more reference counting. Specifically, every time we allocate an object for a program, we're going to
have to increment a count. Each time the program calls that object's Release function, and we free that object, we'll
decrement that same count. Only when the count is zero will we tell the OS that our DLL is safe to unload, because that's when
we know for sure that the program isn't using any of our objects. So, we'll declare a static DWORD variable named
OutstandingObjects to maintain this count. And of course, when our DLL is first loaded, this needs to be initialized to 0.
So, where is the most convenient place to increment this variable? In our IClassFactory's CreateInstance function,
after we actually GlobalAlloc the object and make sure everything went OK. So, we'll add a line in that function, right after
the call to Release:
Hide Copy Code
staticDWORDOutstandingObjects=0;
HRESULTSTDMETHODCALLTYPEclassCreateInstance(IClassFactory*this,
IUnknown*punkOuter,REFIIDvTableGuid,void**ppv)
{
...
IExampleVtbl.Release(thisobj);
//Incrementourcountofoutstandingobjectsifall
//wentwell.
if(!hr)InterlockedIncrement(&OutstandingObjects);;
}
}
return(hr);
}
http://www.codeproject.com/Articles/13601/COMinplainC
14/23
1/21/2016
COMinplainCCodeProject
And where is the most convenient place to decrement this variable? In our IExample's Release function, right after we
GlobalFree the object. So we add a line after GlobalFree:
Hide Copy Code
InterlockedDecrement(&OutstandingObjects);
But there's more. Do the messy details never end with Microsoft? Microsoft has decided that there should be a way for a
program to lock our DLL in memory if it desires. For that purpose, it can call our IClassFactory's LockServer function,
passing a 1 if it wants us to increment a count of locks on our DLL, or 0 if it wants to decrement a count of locks on our DLL. So,
we also need a second static DWORD reference count which we'll call LockCount. And of course, this also needs to be
initialized to 0 when our DLL loads. Our LockServer function now becomes:
Hide Copy Code
staticDWORDLockCount=0;
HRESULTSTDMETHODCALLTYPE
classLockServer(IClassFactory*this,BOOLflock)
{
if(flock)InterlockedIncrement(&LockCount);
elseInterlockedDecrement(&LockCount);
return(NOERROR);
}
Now we're ready to write our DllCanUnloadNow function:
Hide Copy Code
HRESULTPASCALDllCanUnloadNow(void)
{
//Ifsomeonehasretrievedpointerstoanyofourobjects,and
//notyetRelease()'edthem,thenwereturnS_FALSEtoindicate
//nottounloadthisDLL.Also,ifsomeonehasuslocked,return
//S_FALSE
return((OutstandingObjects|LockCount)?S_FALSE:S_OK);
}
If you download the example project, the source file for our DLL IExample.c is in the directory IExample. Also supplied are
Microsoft Visual C++ project files that create a DLL IExample.dll from this source.
typedefHRESULTSTDMETHODCALLTYPEQueryInterfacePtr(IExample*,REFIID,void**);
typedefULONGSTDMETHODCALLTYPEAddRefPtr(IExample*);
typedefULONGSTDMETHODCALLTYPEReleasePtr(IExample*);
typedefHRESULTSTDMETHODCALLTYPESetStringPtr(IExample*,char*);
typedefHRESULTSTDMETHODCALLTYPEGetStringPtr(IExample*,char*,long);
typedefstruct{
http://www.codeproject.com/Articles/13601/COMinplainC
15/23
1/21/2016
COMinplainCCodeProject
QueryInterfacePtr*QueryInterface;
AddRefPtr*AddRef;
ReleasePtr*Release;
SetStringPtr*SetString;
GetStringPtr*GetString;
}IExampleVtbl;
typedefstruct{
IExampleVtbl*lpVtbl;
DWORDcount;
charbuffer[80];
}IExample;
There is one problem with the above. We don't want to let the other program know about our "count" and "buffer"
members. We want to hide them from the program. A program should never be allowed to directly access our object's data
members. It should know only about the "lpVtbl" member so that it can call our functions. So, as far as the program is
concerned, we want our IExample to be defined as so:
Hide Copy Code
typedefstruct{
IExampleVtbl*lpVtbl;
}IExample;
Furthermore, although the typedefs for the function definitions make things easier to read, if you have a lot of functions in
your object, this could get verbose and errorprone.
Finally, there is the problem that the above is a C definition. It really doesn't make things easy for a C++ program which wants
to use our COM object. After all, even though we've written IExample in C, our IExample struct is really a C++ class. And
it's a lot easier for a C++ program to use it defined as a C++ class than a C struct.
Instead of defining things as above, Microsoft provides a macro we can use to define our VTable and object in a way that works
for both C and C++, and hides the extra data members. To use this macro, we must first define the symbol INTERFACE to the
name of our object which in this case is IExample. And prior to that, we must undef that symbol to avoid a compiler
warning. Then, we use the DECLARE_INTERFACE_ macro. Inside of the macro, we list our IExample functions. Here's
what it will look like:
Hide Copy Code
#undefINTERFACE
#defineINTERFACEIExample
DECLARE_INTERFACE_(INTERFACE,IUnknown)
{
STDMETHOD(QueryInterface)(THIS_REFIID,void**)PURE;
STDMETHOD_(ULONG,AddRef)(THIS)PURE;
STDMETHOD_(ULONG,Release)(THIS)PURE;
STDMETHOD(SetString)(THIS_char*)PURE;
STDMETHOD(GetString)(THIS_char*,DWORD)PURE;
};
This probably looks a bit bizarre.
When defining a function, STDMETHOD is used whenever the function returns an HRESULT. Our QueryInterface,
SetString, and GetString functions return an HRESULT. AddRef and Release do not. Those latter two return a
ULONG. So that's why we instead use STDMETHOD_ with an ending underscore for those two. Then, we put the name of the
function in parentheses. If the function doesn't return an HRESULT, we need to put what type it returns, and then a comma,
before the function name. After the function name, we list the function's arguments in parentheses. THIS refers to a pointer to
our object i.e., IExample. If the only thing passed to the function is that pointer, then you simply put THIS in parentheses.
That's the case for the AddRef and Release functions. But the other functions have additional arguments. So, we must use
THIS_ with an ending underscore. Then we list the remaining arguments. Notice that there is no comma between THIS_
and the remaining arguments. But there is a comma in between each of the remaining arguments. Finally, we put the word
http://www.codeproject.com/Articles/13601/COMinplainC
16/23
1/21/2016
COMinplainCCodeProject
typedefstruct{
IExampleVtbl*lpVtbl;
DWORDcount;
charbuffer[80];
}MyRealIExample;
And we'll change a line in our IClassFactory's CreateInstance so that we allocate a MyRealIExample struct:
Hide Copy Code
if(!(thisobj=GlobalAlloc(GMEM_FIXED,sizeof(structMyRealIExample))))
The program doesn't need to know that we're actually giving it an object that has some extra data members inside it which are
for all practical purposes, hidden from that program. After all, both of these structs have the same "lpVtbl" member pointing
to the same array of function pointers. But now, our DLL functions can get access to those "hidden" members just by
typecasting a IExample pointer to a MyRealIExample pointer.
LIBRARYIExample
EXPORTS
DllCanUnloadNowPRIVATE
DllGetClassObjectPRIVATE
17/23
1/21/2016
COMinplainCCodeProject
so that if there is a later version of our DLL already installed there, we don't overwrite it with an earlier version.
We then need to register this DLL. This involves creating several registry keys.
We first need to create a key under HKEY_LOCAL_MACHINE\Software\Classes\CLSID. For the name of this new key, we must
use our IExample object's GUID, but it must be formatted in a particular, text string format.
If you download the example project, the directory RegIExample contains an example installer for IExample.dll. The function
stringFromCLSID demonstrates how to format our IExample GUID into a text string suitable for creating a registry key
name with it.
Note: This example installer does not copy the DLL to some wellchosen location before registering it. Rather, it allows you to
pick out wherever you've compiled IExample.dll and register it in that location. This is just for convenience in developing/testing.
A production quality installer should copy the DLL to a wellchosen location, and do version checking. These needed
enhancements are left for you to do with your own installer.
Under our "GUID key", we must create a subkey named InprocServer32. This subkey's default value is then set to the full path
where our DLL has been installed.
We must also set a value named ThreadingModel to the string value "both", if we don't need to restrict a program to calling
our DLL's functions only from a single thread. Since we don't use global data in our IExample functions, we're threadsafe.
After we run our installer, IExample.dll is now registered as a COM component on our computer, and some program can now
use it.
Note: The directory UnregIExample contains an example uninstaller for IExample.dll. It essentially removes the registry keys that
RegIExample created. A production quality uninstaller should also remove IExample.dll and any directories created by the
installer.
An example C program
Now we're ready to write a C program that uses our IExample COM object. If you download the example project, the
directory IExampleApp contains an example C program.
First of all, the C program #includes our IExample.h include file, so it can reference our IExample object's, and its VTable's,
GUIDs.
Before a program can use any COM object, it must initialize COM, which is done by calling the function CoInitialize. This
need be done only once, so a good place to do it is at the very start of the program.
Next, the program calls CoGetClassObject to get a pointer to IExample.dll's IClassFactory object. Note that we pass
the IExample object's GUID as the first argument. We also pass a pointer to our variable classFactory which is where a
pointer to the IClassFactory will be returned to us, if all goes well.
Once we have the IClassFactory object, we can call its CreateInstance function to get a IExample object. Note
how we use the IClassFactory to call its CreateInstance function. We get the function via IClassFactory's
VTable i.e., its lpVtbl member. Also note that we pass the IClassFactory pointer as the first argument. Remember that
this is standard COM.
Note that we pass IExample's VTable GUID as the third argument. And for the fourth argument, we pass a pointer to our
variable exampleObj which is where a pointer to an IExample object will be returned to us, if all goes well.
Once we have an IExample object, we can Release the IClassFactory object. Remember that a program must call an
object's Release function when done with the object. The IClassFactory is an object, just like IExample is an object.
Each has its own Release function, which must be called when we're done with the object. We don't need the
IClassFactory any more. We don't want to obtain any more IExample objects, nor call any of the IClassFactory's
other functions. So, we can Release it now. Note that this does not affect our IExample object at all.
http://www.codeproject.com/Articles/13601/COMinplainC
18/23
1/21/2016
COMinplainCCodeProject
So next, we call the IClassFactory's Release function. Once we do this, our classFactory variable no longer
contains a valid pointer to anything. It's garbage now.
But we still have our IExample pointer. We haven't yet Released that. So next, we decide to call some of IExample's
functions. We call SetString. Then we follow up with a call to GetString. Note how we use the IExample pointer to
call its SetString function. We get the function via IExample's VTable. And also notice that we pass the IExample
pointer as the first argument. Again, standard COM.
When we're finally done with the IExample, we Release it. Once we do this, our exampleObj variable no longer contains
a valid pointer to anything.
Finally, we must call CoUninitialize to allow COM to clean up some internal stuff. This needs to be done once only, so it's
best to do it at the end of our program but only if CoInitialize succeeded.
There's also a function called CoCreateInstance that can be used to replace the call to
CoGetClassObject to get the DLL's IClassFactory, and then the call to the IClassFactory's
CreateInstance. CoCreateInstance itself calls CoGetClassObject, and then calls the
IClassFactory's CreateInstance. CoCreateInstance directly returns our IExample, bypassing
the need for us to get the IClassFactory. Here's an example use:
Hide Copy Code
if((hr=CoCreateInstance(&CLSID_IExample,0,
CLSCTX_INPROC_SERVER,&IID_IExample,&exampleObj)))
MessageBox(0,"Can'tcreateIExampleobject",
"CoCreateInstanceerror",
MB_OK|MB_ICONEXCLAMATION);
classFactory>lpVtbl>CreateInstance(classFactory,0,
&IID_IExample,&exampleObj);
in C++, we instead code:
Hide Copy Code
classFactory>CreateInstance(0,IID_IExample,&exampleObj);
Note: We also omit the & on the IID_IExample GUID. The GUID macro for C++ doesn't require that it be specified.
19/23
1/21/2016
COMinplainCCodeProject
To create your own object, make a copy of the IExample directory. Delete the Debug and Release subdirectories, and the
following files:
Hide Copy Code
IExample.dsp
IExample.dsw
IExample.ncb
IExample.opt
IExample.plg
In the remaining files IExample.c, IExample.h, IExample.def, search and replace the string IExample with the name of your own
object, for example IMyObject. Rename these files per your new object name i.e., IMyObject.c, etc..
Create a new Visual C++ project with your new object's name, and in this directory. For the type of project, choose "Win32
DynamicLink Library". Create an empty project. Then add the above three files to it.
Make sure you use GUIDGEN.EXE to generate your own GUIDs for your object and its VTable. Do not use the GUIDs that I
generated. Replace the GUID macros in the .H file and remember to replace the <<name>> part of the GUID macro too.
Remove the functions SetString and GetString in the .C and .H files, and add your own functions instead. Modify the
INTERFACE macro in the .H file to define the functions you added.
Change the data members of MyRealIExample i.e., MyRealIMyObject, whatever to what you want.
Modify the installer to change the first three strings in the source.
In the example programs, search and replace the string IExample with the name of your object.
What's next?
Although a C or C++ program, or a program written in most compiled languages, can use our COM object, we have yet to add
some support that will allow most interpreted languages to use our object, such as Visual Basic, VBscript, JScript, Python, etc.
This will be the subject of Part II of this series.
License
This article, along with any associated source code and files, is licensed under The Code Project Open License CPOL
Share
EMAIL
http://www.codeproject.com/Articles/13601/COMinplainC
20/23
1/21/2016
COMinplainCCodeProject
SAPrefs - Netscape-like
Preferences Dialog
Go
First Prev Next
thanks
Member 12163063
3Jan16 17:13
15Nov15 19:38
21/23
1/21/2016
COMinplainCCodeProject
32 or 64 bit
Tachyonx 17Sep15 0:35
typo in the article
Member 1666188
25Feb15 3:17
4Aug13 5:33
My vote of 5
hakz.code 7Jul13 23:26
GUID
LuisR14
16Jun13 4:19
My vote of 5
Victor Noagbodji
My vote of 5
Johannes Frank
7May13 9:41
18Apr13 13:44
My vote of 5
headio 18Dec12 22:54
Excellent Job!
WiStRoM 15Dec12 18:23
Interface is not VTBL
milev yo 5Dec12 16:49
Help for EXE server in com
harshal_2009 5Dec12 1:29
i have a Question ?? in An IClassFactory object
wqliceman 25Oct12 17:52
http://www.codeproject.com/Articles/13601/COMinplainC
22/23
1/21/2016
COMinplainCCodeProject
1 2 3 4 5 6 7 8 9 10 Next
News
Suggestion
Question
Bug
Answer
Joke
Praise
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.160115.1 | Last Updated 29 Mar 2006
http://www.codeproject.com/Articles/13601/COMinplainC
Seleccionar idioma
23/23