Click here to Skip to main content
15,891,253 members
Articles / Programming Languages / C++

COM in plain C, Part 6

Rate me:
Please Sign up or sign in to vote.
4.89/5 (32 votes)
22 Jul 2006CPOL25 min read 103.2K   2.4K   102  
How to write an ActiveX Script Host in C.
<HTML><BODY>
Often, it's convenient for a DLL function we call to "callback" into one of our own functions so that we can do some additional tasks at a particular point, or be notified that something has happened. For example, consider the standard C library function <CODE>qsort</CODE>. The fourth arg passed to qsort is a pointer to some function we provide to compare two items. In this way, when we call qsort, qsort allows us to determine the order that items are sorted, while qsort itself does the work of actually reordering the items.

<P>We can't just provide any arbitrary function to qsort. The guy who wrote qsort specified exactly what arguments would be passed to our "compare" callback function, what our function returns, and of course, specified exactly what is the purpose of our callback function. Furthermore, qsort determines when our callback function is actually called (because it is qsort itself that calls our function). The guy who designed qsort mandated that our compare callback function <U>must</U> be defined as so:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>int</FONT> (<FONT COLOR=BLUE>__cdecl</FONT> *compare)(<FONT COLOR=BLUE>const void</FONT> *elem1, <FONT COLOR=BLUE>const void</FONT> *elem2);</FONT></B></PRE>

<P>Let's create our own version of qsort, which we'll call <CODE>Sort</CODE>. (Actually, we'll cheat to make this simpler, and just do a bubble sort). And let's assume that we'll put this in a DLL named <B>ISort.dll</B>.

<P>Instead of passing <CODE>Sort</CODE> a pointer to the compare function each time, let's design this so that the app will first call a separate <CODE>SetCompare</CODE> function to allow our DLL to store the pointer in some global. Let's also put a function in the DLL called <CODE>UnsetCompare</CODE> that an app can call to clear that function pointer. So here's our DLL source code (that we'll put in a file called ISort.c):

<PRE><B><FONT SIZE=3><FONT COLOR=GREEN>// A global to store the pointer to the app's Compare function</FONT>
<FONT COLOR=BLUE>int</FONT> (STDMETHODCALLTYPE *CompareFunc)(<FONT COLOR=BLUE>const void</FONT> *, <FONT COLOR=BLUE>const void</FONT> *);

<FONT COLOR=BLUE>void</FONT> STDMETHODCALLTYPE SetCompare(
     <FONT COLOR=BLUE>int</FONT> (<FONT COLOR=BLUE>STDMETHODCALLTYPE</FONT> *compare)(<FONT COLOR=BLUE>const void</FONT> *, <FONT COLOR=BLUE>const void</FONT> *))
{
   <FONT COLOR=GREEN>// Save the compare function ptr in a global</FONT>
   CompareFunc = compare;
}

<FONT COLOR=BLUE>void STDMETHODCALLTYPE</FONT> UnsetCompare(<FONT COLOR=BLUE>void</FONT>)
{
   CompareFunc = 0;
}

<FONT COLOR=BLUE>HRESULT</FONT> STDMETHODCALLTYPE Sort(<FONT COLOR=BLUE>void</FONT> *base, <FONT COLOR=BLUE>DWORD</FONT> numElems, <FONT COLOR=BLUE>DWORD</FONT> sizeElem)
{
   <FONT COLOR=BLUE>void</FONT>   *hi;
   <FONT COLOR=BLUE>void</FONT>   *p;
   <FONT COLOR=BLUE>void</FONT>   *lo;
   <FONT COLOR=BLUE>void</FONT>   *tmp;

   <FONT COLOR=GREEN>// Has the app set its Compare function pointer yet?</FONT>
   <FONT COLOR=BLUE>if</FONT> (!CompareFunc) <FONT COLOR=BLUE>return</FONT>(E_FAIL);

   <FONT COLOR=GREEN>// Do the (bubble) sort</FONT>
   <FONT COLOR=BLUE>if</FONT> ((tmp = <FONT COLOR=PURPLE>GlobalAlloc</FONT>(GMEM_FIXED, sizeElem)))
   {
      hi = ((<FONT COLOR=BLUE>char</FONT> *)base + ((numElems - 1) * sizeElem));
      lo = base;

      <FONT COLOR=BLUE>while</FONT> (hi > base)
      {
         lo = base;
         p = ((<FONT COLOR=BLUE>char</FONT> *)base + sizeElem);
         <FONT COLOR=BLUE>while</FONT> (p <= hi)
         {
            <FONT COLOR=BLUE>if</FONT> ((*CompareFunc)(p, lo) &gt; 0) lo = p;
            (<FONT COLOR=BLUE>char</FONT> *)p += sizeElem;
         }

         <FONT COLOR=PURPLE>CopyMemory</FONT>(tmp, lo, sizeElem);
         <FONT COLOR=PURPLE>CopyMemory</FONT>(lo, hi, sizeElem);
         <FONT COLOR=PURPLE>CopyMemory</FONT>(hi, tmp, sizeElem);
         (<FONT COLOR=BLUE>char</FONT> *)hi -= sizeElem;
      }

      <FONT COLOR=PURPLE>GlobalFree</FONT>(tmp);
      <FONT COLOR=BLUE>return</FONT>(S_OK);
   }

   <FONT COLOR=BLUE>return</FONT>(E_OUTOFMEMORY);
}</FONT></B></PRE>

<P>Now let's write an app that uses our DLL to sort an array of 5 DWORDs. First the app calls <CODE>SetCompare</CODE> to specify its compare callback function (which we'll name <CODE>Compare</CODE>). Then, the app calls <CODE>Sort</CODE>. Finally, when our app is finished using Sort, and we wish to make sure that any additional calls to Sort do not accidentally cause it to call our Compare function, we call <CODE>UnsetCompare</CODE>.

<PRE><B><FONT SIZE=3><FONT COLOR=GREEN>// An array of 5 DWORDs to be sorted</FONT>
<FONT COLOR=BLUE>DWORD</FONT> Array[5] = {2, 3, 1, 5, 4};

<FONT COLOR=GREEN>// Our Compare function</FONT>
<FONT COLOR=BLUE>int</FONT> STDMETHODCALLTYPE Compare(<FONT COLOR=BLUE>const void</FONT> *elem1, <FONT COLOR=BLUE>const void</FONT> *elem2)
{
   <FONT COLOR=GREEN>// Do a compare of the two elements. We know that
   // we passed an array of DWORD values to Sort()</FONT>
   <FONT COLOR=BLUE>if</FONT> (*((<FONT COLOR=BLUE>DWORD</FONT> *)elem1) == *((<FONT COLOR=BLUE>DWORD</FONT> *)elem2)) <FONT COLOR=BLUE>return</FONT> 0;
   <FONT COLOR=BLUE>if</FONT> (*((<FONT COLOR=BLUE>DWORD</FONT> *)elem1) &lt; *((<FONT COLOR=BLUE>DWORD</FONT> *)elem2)) <FONT COLOR=BLUE>return</FONT> -1;
   <FONT COLOR=BLUE>return</FONT> 1;
}

<FONT COLOR=BLUE>int</FONT> main(<FONT COLOR=BLUE>int</FONT> argc, <FONT COLOR=BLUE>char</FONT> **argv)
{
   <FONT COLOR=GREEN>// Give ISort.dll our Compare function ptr</FONT>
   <FONT COLOR=PURPLE>SetCompare</FONT>(Compare);

   <FONT COLOR=GREEN>// Sort of array of 5 DWORDs</FONT>
   <FONT COLOR=PURPLE>Sort</FONT>(&amp;Array[0], 5, sizeof(DWORD));

   <FONT COLOR=PURPLE>UnsetCompare</FONT>();

   <FONT COLOR=BLUE>return</FONT> 0;
}</FONT></B></PRE>


<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="WRAP">Wrapping</A> a callback function in a COM object</P></FONT></B></H2>

<P>COM also has a facility to allow callbacks, but as you may have feared, it's not quite as straight-forward as our example above. In the directory named <B>ISort</B>, you'll find the files to a COM object that implements our above Sort DLL.

<P>First of all, we have to wrap our Sort function inside of some COM object. After all, we're going to turn ISort.dll into a COM component. Let's define an ISort object, using the DECLARE_INTERFACE_ macro Microsoft provides. Like all COM objects, its VTable begins with QueryInterface, AddRef, and Release functions. We won't bother adding any IDispatch functions to it. (Our ISort is not useable by script languages such as VBscript). And then we'll add the Sort function as the first extra function. Here's the definition that we'll put in ISort.h:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>#undef</FONT> INTERFACE
<FONT COLOR=BLUE>#define</FONT> INTERFACE  <FONT COLOR=BROWN>ISort</FONT>
DECLARE_INTERFACE_ (INTERFACE, <FONT COLOR=BROWN>IUnknown</FONT>)
{
   STDMETHOD  (QueryInterface) (THIS_ REFIID, void **) PURE;
   STDMETHOD_ (ULONG, AddRef)  (THIS) PURE;
   STDMETHOD_ (ULONG, Release) (THIS) PURE;
   STDMETHOD  (Sort)           (THIS_ void *, DWORD, DWORD) PURE;
};</FONT></B></PRE>

<P>So our app is going to get an instance of our ISort object and call its Sort function. You've now had plenty of experience writing C/C++ apps that get a COM object and call its functions. That's nothing new.

<P>And of course, we need to run GUIDGEN.EXE to generate a GUID for our ISort object and its VTable. We'll give them names of CLSID_ISort and IID_ISort respectively, and put them in ISort.h.

<PRE><B><FONT SIZE=3><FONT COLOR=GREEN>// ISort object's GUID
// {619321BA-4907-4596-874A-AEFF082F0014}</FONT>
<FONT COLOR=BLUE>DEFINE_GUID</FONT>(CLSID_ISort, 0x619321ba, 0x4907, 0x4596,
 0x87, 0x4a, 0xae, 0xff, 0x8, 0x2f, 0x0, 0x14);

<FONT COLOR=GREEN>// ISort VTable's GUID
// {4C9A7D40-D0ED-45ea-9520-1CB9095973F8}</FONT>
<FONT COLOR=BLUE>DEFINE_GUID</FONT>(IID_ISort, 0x4c9a7d40, 0xd0ed, 0x45ea,
 0x95, 0x20, 0x1c, 0xb9, 0x9, 0x59, 0x73, 0xf8);
</FONT></B></PRE>

<P>And like we typically do, we'll need to add at least one, extra, private data member to our ISort object -- a reference count member. So, we'll define a MyRealISort inside of ISort.c that adds this extra private member to our ISort:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>typedef struct</FONT> {
   <FONT COLOR=BLUE>ISortVtbl</FONT>   *lpVtbl;   <FONT COLOR=GREEN>// ISort's VTable</FONT>
   <FONT COLOR=BLUE>DWORD</FONT>       count;     <FONT COLOR=GREEN>// ISort's reference count</FONT>
} MyRealISort;</FONT></B></PRE>

<P>By now, all of this should be quite familiar to you.

<P>All that's left is to provide some way for an app to give us a pointer to its <CODE>Compare</CODE> function so our <CODE>Sort</CODE> function can call it. So, do we simply add <CODE>SetCompare</CODE> and <CODE>UnsetCompare</CODE> functions to our ISort VTable? Nope.

<P>Here's where it starts to get complicated. Microsoft needed some standard way to allow an app to provide its callback functions to a COM object. Microsoft decided that an app should wrap its callback functions in a COM object. What particular COM object? That depends.

<P>Remember how it was the author of qsort who determined what args were passed to the callback, and what it returned? Well, since we're the guys writing our ISort object, we get to make up our own COM object that the app must use to wrap any callback functions we require. So let's just define our own COM object that we'll call an ICompare. Of course, we must start its VTable with the 3 functions QueryInterface, AddRef, and Release. So what do we want next? Let's see... Do we want to support script languages supplying us a compare function (such as a VBscript function)? If so, we'd put some IDispatch functions next. No, let's skip that, and for now, only allow a C/C++ app to provide us with a compare callback.

<P>Let's add an extra function to ICompare. We'll call it <CODE>Compare</CODE>. That will be the app's compare callback.  (So now you see how the app has to wrap its callback in our own defined COM object). Here's the definition of our ICompare (which we'll add to ISort.h):

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>#undef</FONT> INTERFACE
<FONT COLOR=BLUE>#define</FONT> INTERFACE  <FONT COLOR=BROWN>ICompare</FONT>
DECLARE_INTERFACE_ (INTERFACE, <FONT COLOR=BROWN>IUnknown</FONT>)
{
   STDMETHOD  (QueryInterface) (THIS_ REFIID, void **) PURE;
   STDMETHOD_ (ULONG, AddRef)  (THIS) PURE;
   STDMETHOD_ (ULONG, Release) (THIS) PURE;
   STDMETHOD_ (long, Compare)  (THIS_ const void *, const void *) PURE;
};</FONT></B></PRE>

<P>We do need to run GUIDGEN.EXE to create a GUID for our ICompare's VTable. (We won't need a GUID for the object itself). We'll give it the name of DIID_Compare. (Traditionally, Microsoft prepends a D to the names of GUIDs intended for VTables that wrap callbacks. We'll just follow the tradition).

<PRE><B><FONT SIZE=3><FONT COLOR=GREEN>// ICompare VTable's GUID
// {4115B8E2-1823-4bbc-B10D-3D33AAA12ACF}</FONT>
<FONT COLOR=BLUE>DEFINE_GUID</FONT>(DIID_ICompare, 0x4115b8e2, 0x1823, 0x4bbc,
 0xb1, 0xd, 0x3d, 0x33, 0xaa, 0xa1, 0x2a, 0xcf);
</FONT></B></PRE>

<P>Let's go back to our app. Now we have to put a COM object inside of our app code. Specifically, we have to create an ICompare object, and have our <CODE>Compare</CODE> callback be its extra function. Because we'll need only one ICompare object, we'll simply declare it static. We'll also have to <FONT COLOR=BLUE>#include</FONT> ISort.h because that contains the definition of the ICompare object, its VTable, and its GUID. So here's the code we insert to replace the <CODE>Compare</CODE> function in our above app code:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>#include</FONT> "ISort.h"

<FONT COLOR=GREEN>// Here's our app's ICompare object. We need only one of these,
// so we'll just declare it static. That way, we don't have to
// allocate it, free it, nor maintain any reference count.</FONT>
<FONT COLOR=BLUE>static ICompare</FONT>   MyCompare;

<FONT COLOR=GREEN>// This is ICompare's QueryInterface. It returns a pointer
// to our ICompare object.</FONT>
<FONT COLOR=BLUE>HRESULT</FONT> STDMETHODCALLTYPE QueryInterface(<FONT COLOR=BLUE>ICompare</FONT> *this,
      <FONT COLOR=BLUE>REFIID</FONT> vTableGuid, <FONT COLOR=BLUE>void</FONT> **ppv)
{
   <FONT COLOR=GREEN>// Since this is an ICompare object, we must recognize
   // ICompare VTable's GUID, which is defined for us in
   // ISort.h. Our ICompare can also masquerade as an IUnknown.</FONT>
   <FONT COLOR=BLUE>if</FONT> (!<FONT COLOR=PURPLE>IsEqualIID</FONT>(vTableGuid, &IID_IUnknown) &&
      !<FONT COLOR=PURPLE>IsEqualIID</FONT>(vTableGuid, &DIID_ICompare))
   {
      *ppv = 0;
      <FONT COLOR=BLUE>return</FONT>(E_NOINTERFACE);
   }

   *ppv = this;

   <FONT COLOR=GREEN>// Normally, we'd call our AddRef function here. But since
   // our ICompare isn't allocated, we don't need to bother
   // with reference counting.</FONT>
   <FONT COLOR=BLUE>return</FONT>(NOERROR);
}

<FONT COLOR=BLUE>ULONG</FONT> STDMETHODCALLTYPE AddRef(<FONT COLOR=BLUE>ICompare</FONT> *this)
{
   <FONT COLOR=GREEN>// Our one and only ICompare isn't allocated. Instead, it's
   // statically declared above (MyCompare). So we'll just
   // return 1.</FONT>
   <FONT COLOR=BLUE>return</FONT>(1);
}

<FONT COLOR=BLUE>ULONG</FONT> STDMETHODCALLTYPE Release(<FONT COLOR=BLUE>ICompare</FONT> *this)
{
   <FONT COLOR=GREEN>// Our one and only ICompare isn't allocated, so we
   // never need worry about freeing it.</FONT>
   <FONT COLOR=BLUE>return</FONT>(1);
}

<FONT COLOR=GREEN>// This is the extra function for ICompare. It is called
// Compare. The ISort object will call this function when
// we call ISort's Sort function.</FONT>
<FONT COLOR=BLUE>long</FONT> STDMETHODCALLTYPE Compare(<FONT COLOR=BLUE>ICompare</FONT> *this,
      <FONT COLOR=BLUE>const void</FONT> *elem1, <FONT COLOR=BLUE>const void</FONT> *elem2)
{
   <FONT COLOR=GREEN>// Do a compare of the two elements. We know that
   // we passed an array of DWORD values to Sort().</FONT>
   <FONT COLOR=BLUE>if</FONT> (*((<FONT COLOR=BLUE>DWORD</FONT> *)elem1) == *((<FONT COLOR=BLUE>DWORD</FONT> *)elem2)) <FONT COLOR=BLUE>return</FONT> 0;
   <FONT COLOR=BLUE>if</FONT> (*((<FONT COLOR=BLUE>DWORD</FONT> *)elem1) &lt; *((<FONT COLOR=BLUE>DWORD</FONT> *)elem2)) <FONT COLOR=BLUE>return</FONT> -1;
   <FONT COLOR=BLUE>return</FONT> 1;
}

<FONT COLOR=GREEN>// Our ICompare VTable. We need only one of these, so we can
// declare it static.</FONT>
<FONT COLOR=BLUE>static const ICompareVtbl</FONT> ICompare_Vtbl = {QueryInterface,
AddRef,
Release,
Compare};</FONT></B></PRE>

<P>Notice that our <CODE>Compare</CODE> function is there. It's simply wrapped inside of an ICompare object now. (And the first arg passed to it is now a pointer to our ICompare object, which will be our statically declared MyCompare in this case).

<P>Now we're left with the really convoluted job. How does our app give its ICompare object to our ISort object (inside of ISort.dll)? I really wish I could say that this is going to be simple and easy-to-understand, but unfortunately, due to some dubious design decisions by the MS guys who devised this whole scheme, it's going to be a painfully twisted trip through the house of horrors.

<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="CONNECT">IConnectionPointContainer</A> and IConnectionPoint objects</P></FONT></B></H2>

<P>To let an app give its ICompare object to our ISort object, we have to add some things to our ISort object. Specifically, we have to add two sub-objects to it. (Hopefully, in the last chapter, you have read and understand how to create an object that has sub-objects).

<P>One good thing is that Microsoft has already defined these two sub-objects (and their VTable GUIDs) in some include files that ship with your compiler. The two sub-objects are called IConnectionPointContainer and IConnectionPoint. All we need to do is add them to our ISort object. Since we've already defined a MyRealISort, we'll just embed these two sub-objects inside it. And since we know that an app is going to give us a pointer to an ICompare object, let's also add a place to store that inside of MyRealISort.

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>typedef struct</FONT> {
   <FONT COLOR=BLUE>ISortVtbl</FONT>                 *lpVtbl;
   <FONT COLOR=BLUE>DWORD</FONT>                     count;
   <FONT COLOR=BLUE>IConnectionPointContainer</FONT> container;
   <FONT COLOR=BLUE>IConnectionPoint</FONT>          point;
   <FONT COLOR=BLUE>ICompare</FONT>                  *compare;
} MyRealISort;</FONT></B></PRE>

<P>Note that our base object is our ISort itself. We could notate it like so:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>typedef struct</FONT> {
   <FONT COLOR=BLUE>ISort</FONT>                     iSort;
   <FONT COLOR=BLUE>DWORD</FONT>                     count;
   <FONT COLOR=BLUE>IConnectionPointContainer</FONT> container;
   <FONT COLOR=BLUE>IConnectionPoint</FONT>          point;
   <FONT COLOR=BLUE>ICompare</FONT>                  *compare;
} MyRealISort;</FONT></B></PRE>

<P>But since an ISort has only its one lpVtbl member, both definitions amount to the same thing.

<P>As you'll remember from the last chapter, the base object's QueryInterface must recognize the GUIDs of all of its sub-object's VTables, as well as its own VTable. So our ISort QueryInterface must look like so:


<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>HRESULT</FONT> STDMETHODCALLTYPE QueryInterface(<FONT COLOR=BLUE>ISort</FONT> *this, <FONT COLOR=BLUE>REFIID</FONT> vTableGuid, <FONT COLOR=BLUE>void</FONT> **ppv)
{
   <FONT COLOR=GREEN>// Because an IConnectionPointContainer is a sub-object of our ISort, we
   // must return a pointer to this sub-object if the app asks for it. Because
   // we've embedded our IConnectionPointContainer object inside of our
   // MyRealISort, we can get that sub-object very easily using pointer arithmetic.</FONT>
   <FONT COLOR=BLUE>if</FONT> (<FONT COLOR=PURPLE>IsEqualIID</FONT>(vTableGuid, &IID_IConnectionPointContainer))
      *ppv = ((<FONT COLOR=BLUE>unsigned char</FONT> *)this + <FONT COLOR=BLUE>offsetof</FONT>(MyRealISort, container));

   <FONT COLOR=BLUE>else if</FONT> (<FONT COLOR=PURPLE>IsEqualIID</FONT>(vTableGuid, &IID_IUnknown) || <FONT COLOR=PURPLE>IsEqualIID</FONT>(vTableGuid, &IID_ISort))
      *ppv = this;

   <FONT COLOR=BLUE>else</FONT>
   {
      *ppv = 0;
      <FONT COLOR=BLUE>return</FONT>(E_NOINTERFACE);
   }

   this->lpVtbl-><FONT COLOR=PURPLE>AddRef</FONT>(this);

   <FONT COLOR=BLUE>return</FONT>(NOERROR);
}</FONT></B></PRE>

<P>But wait. Did we forget to also check for our IConnectionPoint sub-object's VTable GUID, and return a pointer to that? No, we didn't forget. Unfortunately, the MS guys who thought up this design kind of broke the rules. The IConnectionPoint sub-object is not gotten by calling the base object's QueryInterface (nor the QueryInterface of our IConnectionPointContainer sub-object). In a moment, we'll see how it's gotten.

<P>The IConnectionPointContainer sub-object can serve two purposes.

<P>First of all, by passing an IConnectionPointContainer VTable GUID to our ISort's QueryInterface function, an app can determine whether our ISort accepts callback functions. Incidentally, in COM documentation, such an object (ie, one that provides an IConnectionPointContainer) is described as <I>sourcing events</I>. If QueryInterface returns a pointer to an IConnectionPointContainer, then the object can source events. (If not, then a 0 is returned).

<P>Secondly, the IConnectionPointContainer has a function to get an IConnectionPoint sub-object. That function is called <CODE>FindConnectionPoint</CODE>.

<P>So let's write the functions of our IConnectionPointContainer sub-object. Because it's a sub-object of ISort, and ISort is the base object, our IConnectionPointContainer's QueryInterface, AddRef, and Release functions simply delegate to ISort's QueryInterface, AddRef, and Release.

<PRE><B><FONT SIZE=3>STDMETHODIMP QueryInterface_Connect(<FONT COLOR=BLUE>IConnectionPointContainer</FONT> *this,
     <FONT COLOR=BLUE>REFIID</FONT> vTableGuid, <FONT COLOR=BLUE>void</FONT> **ppv)
{
   <FONT COLOR=GREEN>// Because this is a sub-object of our ISort (ie, MyRealISort) object,
   // we delegate to ISort's QueryInterface. And because we embedded the
   // IConnectionPointContainer directly inside of MyRealISort, all we need
   // is a little pointer arithmetic to get our ISort.</FONT>
   <FONT COLOR=BLUE>return</FONT>(<FONT COLOR=PURPLE>QueryInterface</FONT>((<FONT COLOR=BLUE>ISort</FONT> *)((<FONT COLOR=BLUE>char</FONT> *)this - <FONT COLOR=BLUE>offsetof</FONT>(MyRealISort, container)),
         vTableGuid, ppv));
}

STDMETHODIMP_(ULONG) AddRef_Connect(<FONT COLOR=BLUE>IConnectionPointContainer</FONT> *this)
{
   <FONT COLOR=GREEN>// Because we're a sub-object of ISort, delegate to its AddRef()
   // in order to increment ISort's reference count.</FONT>
   <FONT COLOR=BLUE>return</FONT>(<FONT COLOR=PURPLE>AddRef</FONT>((<FONT COLOR=BLUE>ISort</FONT> *)((<FONT COLOR=BLUE>char</FONT> *)this - <FONT COLOR=BLUE>offsetof</FONT>(MyRealISort, container))));
}

STDMETHODIMP_(ULONG) Release_Connect(<FONT COLOR=BLUE>IConnectionPointContainer</FONT> *this)
{
   <FONT COLOR=GREEN>// Because we're a sub-object of ISort, delegate to its Release()
   // in order to decrement ISort's reference count.</FONT>
   <FONT COLOR=BLUE>return</FONT>(<FONT COLOR=PURPLE>Release</FONT>((<FONT COLOR=BLUE>ISort</FONT> *)((<FONT COLOR=BLUE>char</FONT> *)this - <FONT COLOR=BLUE>offsetof</FONT>(MyRealISort, container))));
}</FONT></B></PRE>


<P>For now, we're going to use a stub for the <CODE>EnumConnectionPoints</CODE> function.

<PRE><B><FONT SIZE=3>STDMETHODIMP EnumConnectionPoints(<FONT COLOR=BLUE>IConnectionPointContainer</FONT> *this,
    <FONT COLOR=BLUE>IEnumConnectionPoints</FONT> **enumPoints)
{
   *enumPoints = 0;
   <FONT COLOR=BLUE>return</FONT>(E_NOTIMPL);
}</FONT></B></PRE>

<P>The real work is in <CODE>FindConnectionPoint</CODE>. The app passes us a handle to where it wants us to return ISort's IConnectionPoint sub-object. Since we've embedded it directly inside of MyRealISort, it's easy to locate.

<P>The app also passes us the VTable GUID we defined for our ICompare (callback) object. That's how we know that the app really does mean to provide us with the correct object that we require. We don't want to return a IConnectionPoint if the app isn't prepared to give us an ICompare object.

<PRE><B><FONT SIZE=3>STDMETHODIMP FindConnectionPoint(<FONT COLOR=BLUE>IConnectionPointContainer</FONT> *this,
    <FONT COLOR=BLUE>REFIID</FONT> vTableGuid, <FONT COLOR=BLUE>IConnectionPoint</FONT> **ppv) 
{
   <FONT COLOR=GREEN>// Is the app asking us to return an IConnectionPoint object it can use
   // to give us its ICompare object? The app asks this by passing us
   // ICompare VTable's GUID (which we defined in ISort.h).</FONT>
   <FONT COLOR=BLUE>if</FONT> (<FONT COLOR=PURPLE>IsEqualIID</FONT>(vTableGuid, &DIID_ICompare))
   {
      <FONT COLOR=BLUE>MyRealISort</FONT>  *iSort;

      <FONT COLOR=GREEN>// The app obviously wants to give its ICompare object to our
      // ISort. In order to do that, we need to give the app a standard
      // IConnectionPoint. This is easy to do since we embedded both
      // our IConnectionPointContainer and IConnectionPoint inside of our 
      // ISort. All we need is a little pointer arithmetic.</FONT>
      iSort = (<FONT COLOR=BLUE>MyRealISort</FONT> *)((<FONT COLOR=BLUE>char</FONT> *)this - <FONT COLOR=BLUE>offsetof</FONT>(MyRealISort, container));
      *ppv = &amp;iSort->point;

      <FONT COLOR=GREEN>// Because we're giving the app a pointer to our IConnectionPoint,
      // and our IConnectionPoint is a sub-object of ISort, we need to
      // increment ISort's reference count. The easiest way to do this is
      // to call our IConnectionPointContainer's AddRef, because all we do
      // there is delegate to our ISort's AddRef.</FONT>
      <FONT COLOR=PURPLE>AddRef_Connect</FONT>(this);

      <FONT COLOR=BLUE>return</FONT>(S_OK);
   }

   <FONT COLOR=GREEN>// We don't support any other app callback objects for our ISort.
   // All we've defined, and support, is an ICompare object. Tell
   // the app we don't know anything about the GUID he passed to us, and
   // do not give him any IConnectionPoint object.</FONT>
   *ppv = 0;
   <FONT COLOR=BLUE>return</FONT>(E_NOINTERFACE);
}</FONT></B></PRE>

<P>That takes care of our IConnectionPointContainer's functions. Now we need to write our IConnectionPoint's functions.

<P>Since IConnectionPoint is also a sub-object of ISort (just like IConnectionPointContainer), its QueryInterface, AddRef, and Release will delegate to ISort's QueryInterface, AddRef, and Release. I won't bother reproducing those functions since they are almost identical to IConnectionPointContainer's.

<P>For now, we're going to use a stub for the <CODE>EnumConnections</CODE> function.

<P>The <CODE>GetConnectionInterface</CODE> function simply copies ICompare's VTable GUID to a buffer that the app passes. Later, we'll see the use for this.

<P>The <CODE>GetConnectionPointContainer</CODE> function returns a pointer to the IConnectionPointContainer sub-object that created this IConnectionPoint object. What this implies is that, as long as the app is holding onto some IConnectionPoint sub-object that our IConnectionPointContainer gives to the app, then our IConnectionPointContainer must stick around. This is no problem here since we've embedded both our IConnectionPointContainer and IConnectionPoint inside of our MyRealISort (and our MyRealISort is going to stick around until all its sub-objects and base object are Release'ed).

<P>The above 3 functions are fairly trivial, so I'll simply refer you to the source code in ISort.c.

<P>The real work is in the <CODE>Advise</CODE> and <CODE>Unadvise</CODE> functions. This is how an app actually gives us its ICompare. <CODE>Advise</CODE> essentially does the same task as our <CODE>SetCompare</CODE> function did. And <CODE>Unadvise</CODE> does what our <CODE>UnsetCompare</CODE> did.

<P>Let's look at <CODE>Advise</CODE>:

<PRE><B><FONT SIZE=3>STDMETHODIMP Advise(<FONT COLOR=BLUE>IConnectionPoint</FONT> *this, <FONT COLOR=BLUE>IUnknown</FONT> *obj, <FONT COLOR=BLUE>DWORD</FONT> *cookie) 
{
   <FONT COLOR=BLUE>HRESULT</FONT>     hr;
   <FONT COLOR=BLUE>MyRealISort</FONT> *iSort;

   <FONT COLOR=GREEN>// Get our MyRealISort that this IConnectionPoint sub-object belongs
   // to. Because this IConnectPoint sub-object is embedded directly
   // inside its MyRealISort, all we need is a little pointer arithmetic.</FONT>
   iSort = (<FONT COLOR=BLUE>MyRealISort</FONT> *)((<FONT COLOR=BLUE>char</FONT> *)this - <FONT COLOR=BLUE>offsetof</FONT>(MyRealISort, point));

   <FONT COLOR=GREEN>// We allow only one ICompare for our ISort, so see if the app already
   // called our Advise(), and we got one. If so, let the app know that it
   // is trying to give us more ICompares than we allow.</FONT>
   <FONT COLOR=BLUE>if</FONT> (iSort->compare) <FONT COLOR=BLUE>return</FONT>(CONNECT_E_ADVISELIMIT);
 
   <FONT COLOR=GREEN>// Ok, we haven't yet gotten the one ICompare we allow from the app. Get
   // the app's ICompare object. We do this by calling the QueryInterface
   // function of the app object passed to us. We pass ICompare VTable's
   // GUID (which we defined in ISort.h).
   //
   // Save the app's ICompare pointer in our ISort compare member, so we
   // can get it when we need it.</FONT>
   hr = obj->lpVtbl-><FONT COLOR=PURPLE>QueryInterface</FONT>(obj, &DIID_ICompare, &iSort->compare);

   <FONT COLOR=GREEN>// We need to return (to the app) some value that will clue our Unadvise()
   // function below how to locate this app ICompare. The simpliest thing is
   // to just use the app's ICompare pointer as that return value.</FONT>
   *cookie = (<FONT COLOR=BLUE>DWORD</FONT>)iSort->compare;

   <FONT COLOR=BLUE>return</FONT>(hr);
}</FONT></B></PRE>

<P>Actually, the app doesn't pass a pointer to its ICompare object. That would be too easy, straight-forward, and understandable, and obviously that wasn't the goal for the MS programmers who designed this thing. Rather, the app passes us some app object from which we can request the app to give us its ICompare. Our Advise function is expected to call that object's QueryInterface function, passing ICompare VTable's GUID  to request the app's ICompare. I guess that MS thought this would be a good way to double-check that the app really is giving us an ICompare (except don't you know that if an app is so badly written it can't figure out what it should be doing with Advise, then its QueryInterface will probably return the wrong object too). Our course, the app's QueryInterface will automatically do an AddRef on the ICompare it gives to us, so we are expected to Release the app's ICompare when done with it.

<P>Our Advise function stores the app's ICompare pointer in our MyRealISort's <CODE>compare</CODE> member, so we can access it when we need it, and finally Release it.

<P>Advise must return some DWORD value, of our own choosing, to the app. The app is expected to store this DWORD value, and later pass it to our Unadvise function. We can choose any DWORD value we want, but this value should somehow help our <CODE>Unadvise</CODE> function when the app later wants us to give back (ie, Release) its ICompare. Indeed, the app later calls our Unadvise when the app no longer wants us to call its callback functions, and to instead Release its ICompare. So let's look at Unadvise:

<PRE><B><FONT SIZE=3>STDMETHODIMP Unadvise(<FONT COLOR=BLUE>IConnectionPoint</FONT> *this, <FONT COLOR=BLUE>DWORD</FONT> cookie) 
{
   <FONT COLOR=BLUE>MyRealISort</FONT> *iSort;

   <FONT COLOR=GREEN>// Get the MyRealISort that this IConnectionPoint sub-object belongs
   // to. Because this IConnectPoint sub-object is embedded directly
   // inside its MyRealISort, all we need is a little pointer arithmetic.</FONT>
   iSort = (<FONT COLOR=BLUE>MyRealISort</FONT> *)((<FONT COLOR=BLUE>char</FONT> *)this - <FONT COLOR=BLUE>offsetof</FONT>(MyRealISort, point));

   <FONT COLOR=GREEN>// Use the passed value to find wherever we stored his ICompare pointer.
   // Well, since we allow only one ICompare for our ISort, we already
   // know we stored it in our ISort->compare member. And Advise()
   // returned that pointer as the "cookie" value. So we already got the
   // ICompare right now.
   //		
   // Let's just make sure the cookie he passed is really the pointer we
   // expect.</FONT>
   <FONT COLOR=BLUE>if</FONT> (cookie && (<FONT COLOR=BLUE>ICompare</FONT> *)cookie == iSort->compare)
   {
      <FONT COLOR=GREEN>// Release the app's ICompare</FONT>
      ((<FONT COLOR=BLUE>ICompare</FONT> *)cookie)->lpVtbl-><FONT COLOR=PURPLE>Release</FONT>((<FONT COLOR=BLUE>ICompare</FONT> *)cookie);

      <FONT COLOR=GREEN>// We no longer have the app's ICompare, so clear the ISort
      // compare member.</FONT>
      iSort->compare = 0;
      
      <FONT COLOR=BLUE>return</FONT>(S_OK);
   }
   <FONT COLOR=BLUE>return</FONT>(CONNECT_E_NOCONNECTION);
}</FONT></B></PRE>

<P>The rest of the code in ISort.c is almost exactly the same as in our very first chapter's IExample.c. The only difference is that, after our IClassFactory's <CODE>CreateInstance</CODE> allocates our MyRealISort, we must initialize the sub-objects inside of it, and initialize any other private data members.

<P>You can compile our COM object into ISORT.DLL. To register it, simply take the register utility for IExample (in the directory <B>RegIExample</B>) and replace all instances of IExample with ISort.

<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="C1">An</A> example C app</P></FONT></B></H2>

<P>In the directory <B>ISortApp</B> is an example C app that uses our ISort DLL. Previously, we discussed the ICompare functions, so all that's really new here is how the app calls our Advise function to give us its ICompare object, and then later calls Unadvice to ask us to release it. This is a fairly trivial example, so you can peruse the comments in ISortApp.c.

<P>Because COM event connections can be fairly complicated, I've supplied a second example of an object that uses callbacks. In the directory <B>IExampleEvts</B> is the source to another COM DLL. This DLL implements an object (IExampleEvts) that has an extra function called <CODE>DoSomething</CODE>. This function has the ability to call any of upto 5 different app callbacks. So we have defined an IFeedback object to wrap all 5 of those callback functions. Its definition is in IExampleEvts.h. (ie, A callback object can contain numerous extra, callback functions. And each function can have a different purpose, take different args, and return different values).

<P>Take the time to peruse these examples and fully understand what is going on. Things are going to get more complicated as we go on from here.

<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="SCRIPT">Adding</A> support for script languages</P></FONT></B></H2>

<P>In order to support script languages, we need to add some IDispatch functions. Let's take our IExampleEvts COM object and adapt it for use with script languages.

<P>First, let's create a new directory named IExampleEvts2, and copy the original sources there. Then we'll rename the files, replacing all instances of IExampleEvts with IExampleEvts2, and all instances of IFeedback with IFeedback2. I've done this for you already. I also ran GUIDGEN.EXE and made new GUIDs for the objects and their VTables. Since we're going to also need a type library, I created a new GUID for that too.

<P>We need to add IDispatch functions to both our IExampleEvts2 VTable, and our IFeedback2 VTable. You can compare the original IExampleEvts.h with the new IExampleEvts2.h to see the changes. Note that both VTables now have the 5 standard IDispatch functions added, and also the macros now specify that both VTables are based on an IDispatch (instead of IUnknown).

<P>In IExampleEvts2.c, I've added the 5 IDispatch functions to our IExampleEvts2 object and its statically declared VTable. I also added a static variable <CODE>MyTypeInfo</CODE> to hold an ITypeInfo that Microsoft's <CODE>GetTypeInfoOfGuid</CODE> creates for us (and which we use with <CODE>DispInvoke</CODE>). This is same thing we did when we added support to our IExample2 object back in chapter 2. There's nothing new to any of these changes so far.

<P>We do have to change our <CODE>DoSomething</CODE> function. No longer can we call any callback function directly (referencing it via the lpVtbl of the object). This is because a script engine will not actually have function pointers in its VTable for those extra callback functions. Instead, we must call it indirectly using the callback object's (IFeedback's) <CODE>Invoke</CODE> function. We must pass the appropriate DISPID of the function we wish to call.

<P>The only thing new appears in our IExampleEvts2.IDL file (ie, the file that MIDL.EXE compiles into our .TLB type library file). Notice how we're defined IFeedback2's VTable (which we arbitrarily label as IFeedback2VTbl). This is similiar to how we define our IExampleEvts2 VTable, except that we don't use the dual keyword on IFeedback2's VTable. Script engines can't utilize a dual VTable for a callback object. I've arbitrarily chosen to use DISPID 1 for the first callback function (Callback1), 2 for the second callback function, etc.

<P>The other new thing is how we define our IExampleEvts2 object itself as so:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>coclass</FONT> IExampleEvts2
{
   [<FONT COLOR=BLUE>default</FONT>] <FONT COLOR=BLUE>interface</FONT> IExampleEvts2VTbl;
   [<FONT COLOR=BLUE>source</FONT>, <FONT COLOR=BLUE>default</FONT>] <FONT COLOR=BLUE>interface</FONT> IFeedback2VTbl;
}</FONT></B></PRE>

<P>The first interface line should look familiar. It's the same as how we've always marked an object's VTable in any IDL file. But the second interface line tells anyone using our type library that IExampleEvts2 supports a callback object whose VTable is described by IFeedback2Vtbl. The "source" keyword indicates this is a callback VTable. The "default" keyword means that this VTable is the one a script engine should use for any callback object associated with our IExampleEvts2 object.

<P>You can compile our COM object into IExampleEvts2.DLL. To register it, simply take the register utility for IExample2 (in the directory <B>RegIExample2</B>) and replace all instances of IExample2 with IExampleEvts2.

<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="C2">Another</A> example C app</P></FONT></B></H2>

<P>In the directory <B>IExampleEvts2App</B> is an example C app that uses our IExampleEvts2 DLL. Compare this with the original source in <B>IExampleEvtsApp</B>. Notice that we're added the 5 standard IDispatch functions to our IFeedback2 object.

<P>Although IExampleEvts2.IDL did not define the IFeedback2 VTable as "dual", we've nevertheless included the extra functions (<CODE>Callback1</CODE> through <CODE>Callback5</CODE>) in our actual statically declared VTable (IFeedback2_Vtbl). In this way, we can fool <CODE>DispInvoke</CODE> into using this VTable as if it really was declared as dual. Also notice that the type library we load is IExampleEvts2's type library, because that is where the IFeedback2 VTable is described. And the ITypeInfo we ask <CODE>GetTypeInfoOfGuid</CODE> to create is for an IFeedback2 VTable. We use this ITypeInfo with our IFeedback2's <CODE>Invoke</CODE> function.

<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="VB">A</A> VBscript example</P></FONT></B></H2>

<P>In the IExampleEvts2 directory is a VBscript example named <B>IExampleEvts2.vbs</B> as so:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>Set</FONT> myobj = <FONT COLOR=PURPLE>WScript.CreateObject</FONT>(<FONT COLOR=RED>"IExampleEvts2.object"</FONT>, <FONT COLOR=RED>"label_"</FONT>)
myobj.<FONT COLOR=PURPLE>DoSomething</FONT>()

<FONT COLOR=BLUE>sub</FONT> label_Callback1()
  <FONT COLOR=PURPLE>Wscript.echo</FONT> <FONT COLOR=RED>"Callback1 is called"</FONT>
<FONT COLOR=BLUE>end sub</FONT></FONT></B></PRE>

The first line uses the Windows Scripting Shell's <CODE>CreateObject</CODE> to get an instance of our IExampleEvts2 object (which should have been registered with the ProdID of </I>IExampleEvts2.object</I>). The reason we use the Script Shell's CreateObject instead of the VB engine's CreateObject, is because the former allows us to pass a second arg -- a string that is prepended to the name of any callback function. Here, we've specified the string <I>label</I>. So for example, when our IExampleEvts2 <CODE>DoSomething</CODE> calls the script engine's IFeedback2's <CODE>Invoke</CODE> and passes a DISPID of 1 (ie, for Callback1), then the VBscript function that gets called is <CODE>label_Callback1</CODE>.

<P>In fact, we've provided a VB function by that name, which simply displays a message box.

<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="MULT">Multiple</A> types of callback objects</P></FONT></B></H2>

<P>An object can support numerous types of callback objects. For example, if we wanted, we could have our ISort object support being given both an ICompare and IFeedback2 callback objects. In this case, we would need a separate IConnectionPoint object for each. We'd also need to put both the ICompare and IFeedback2 VTables in our IDL file, and reference them both in our object like so:

<PRE><B><FONT SIZE=3><FONT COLOR=BLUE>coclass</FONT> IExampleEvts2
{
   [<FONT COLOR=BLUE>default</FONT>] <FONT COLOR=BLUE>interface</FONT> IExampleEvts2VTbl;
   [<FONT COLOR=BLUE>source</FONT>, <FONT COLOR=BLUE>default</FONT>] <FONT COLOR=BLUE>interface</FONT> IFeedback2VTbl;
   [<FONT COLOR=BLUE>source</FONT>] <FONT COLOR=BLUE>interface</FONT> ICompareVtbl;
}</FONT></B></PRE>

<P>But note that only one source interface can be marked as default, and a script engine can use only the default one. (ie, A script can't provide us any ICompare callback functions above).

<P>Furthermore, we have to provide real code for our IConnectionPointContainer's <CODE>EnumConnectionPoints</CODE> function. This function should return another object that can enumerate each of the different types of IConnectPoints that we support. (The object it returns is a standard enumerator object such as the enumerator we used in our Collections chapter).

<P>Because callback objects are usually used in conjunction with scripting (and script engines can't support multiple types of callback objects per a single coclass object), I'm not going to delve further into multiple types of callback objects.

<P><H2><B><FONT COLOR=BROWN><P ALIGN=CENTER><A NAME="MULT2">Multiple</A> callback objects</P></FONT></B></H2>

<P>In our example objects, we allowed an app (or script engine) to provide us only one IFeedback2 object (which we stored in our MyRealIExampleEvts2's <CODE>feedback</CODE> member). But theoretically, an app may provide us with as many  IFeedback2 callback objects as it desires. In our IConnectionPoint's <CODE>Advise</CODE>, we simply refused to accept more than one IFeedback2 from an app/engine. If you wish to allow an app/engine to provide more IFeedback2's, then instead of a <CODE>feedback</CODE> member, you may wish to define another struct that can be linked into a list, and also has a member to store an IFeedback2 pointer. You would GlobalAlloc one of these structs in Advise, and link them into a list whose head would be stored in some MyRealIExampleEvts2 member.

<P>You would also need to modify <CODE>DoSomething</CODE> to loop through the entire list, and call each IFeedback2's Invoke.

<P>But again, since the primary use of callback objects is for scripting, and most uses will involve only one callback object, we'll skip developing an example to illustrate.
</BODY></HTML>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions