In my previous article "Getting
the most out of IDispatch", I introduced a C++ class, XYDispDriver,
which can be used to create any COM object (well, almost any) and call
its methods. With this class, we no longer need a separate
wrapper class for each type of COM object we want to use. For those
of you who are familiar with the #import
directive, we don't need that either (not that it is not useful).
To call a method in a COM object using XYDispDriver, we only need to know the parameter types and
return type (although we can get this info dynamically). In other words, we
need the COM interface, not the COM object itself. As I understand it,
a COM interface is just a contract between
the users and the implementer of the COM object. So there is nothing
to prevent us from writing code that uses a COM object before the COM object
is actually implemented, and you can do that easily with the help
of XYDispDriver. Of course, you
have to create a dummy COM object to test the code or wait until after
the COM object is implemented to test.
As some readers have pointed out, XYDispDriver
uses IDispatch::Invoke to call COM methods indirectly, which is
called late-binding. Late-binding is not as efficient as early-binding,
which calls the COM methods directly. If you have looked at the .tlh
files generated by the #import directive, you will see that it uses
both early-binding and late-binding. To do early-binding, at least as I thought previously,
the COM object has to be implemented first.
But I was wrong. If we already know the COM interface (the contract),
early-binding is actually possible without the COM object being implemented.
I wrote the following console application that early-binds to a non-existent
COM object. We assume that the prog id of the non-existent
COM object is TestObj.1, the COM interface
is derived from IDispatch, and there is only one method called Test
which takes an input string parameter and an output string parameter.
As you can see, it is very easy to do early-binding. Here is the
code.
#include <stdio.h>
#include <oaidl.h>
interface ITestObj : public
IDispatch
{
public:
virtual HRESULT STDMETHODCALLTYPE Test(BSTR strInput, BSTR* pOutput) = 0;
};
void main()
{
::CoInitialize(NULL);
CLSID clsidObj;
HRESULT hRet = ::CLSIDFromProgID(L"TestObj.1", &clsidObj);
if(hRet==S_OK)
{
ITestObj* pDisp = NULL;
hRet = ::CoCreateInstance(clsidObj,NULL,CLSCTX_ALL,IID_IDispatch,(void**)&pDisp);
if(hRet==S_OK)
{
BSTR strInput = ::SysAllocString(L"This is test 3");
BSTR strOutput = NULL;
hRet = pDisp->Test(strInput,&strOutput);
if(hRet==S_OK)
{
wprintf(L"%s\n",strOutput);
::SysFreeString(strOutput);
}
else printf("Error: %s",hRet);
::SysFreeString(strInput);
pDisp->Release();
}
else printf("Error: %x",hRet);
}
else printf("Error: %x",hRet);
::CoUninitialize();
}
The above console app will compile fine without the actual COM object.
Now you can use the ATL COM Wizard to generate a project for the
COM object and implement the Test method
as specified in the COM interface. After building the COM object,
run the console app to verify that early-binding works! You can,
of course, use the same technique for more complicated COM interfaces.
The code in this article is mainly for educational and entertainment purposes.
You can find more code and articles from my home
page. Thank you.