Introduction
One of the advantages of COM is the ability to dynamically link to other COM objects. I learnt to take advantage of this aspect during a project when customers would start saying "Can you find a way to call some program just before you run your program or just after." And of course each customer wanted a different process called. To cater to their needs we (I must include fellow developer and friend Estelle Mangeney who helped in creating this class) started placing calls to functions such as OnStart
and OnEnd
(in reality there were more than 100 such functions called from various objects in our code). For each function I defined and documented the prototype and told the customers that as long as they had a COM object (The ProgID of which was in the database) I would call the functions.
The problem was that I hated having to write all the code for calling Invoke for each of these functions. Each time I wrote the call to a new function using Invoke I made new errors and the whole process was rather painful. The result was
SRComHelper
a class that provides a slightly friendlier interface. I say slightly as I believe it can be made even better but I am often too busy and way too lazy.
Calling Invoke
The process of having to call Invoke for a function can be tedious. For example if there was a COM object
SampleComDLL.DLL with a progID of "SampleComDLL.ComTest" with a function
HRESULT Sum(VARIANT num1, VARIANT num2, [out, retval] VARIANT* sum)
then the process of calling this function would be somewhat like:
void CallInvoke() {
int number1 = 10;
int number2 = 20;
int sum1And2 = 0;
CLSID clsid;
IDispatchPtr disp;
HRESULT result = CLSIDFromProgID( progID, &clsid );
if( SUCCEEDED( result ) ) {
result = disp.CreateInstance(clsid);
}
else {
Some error stuff;
}
EXCEPINFO *pExcepInfo = NULL;
unsigned int *puArgErr = NULL;
DISPID functionID[1];
VARIANT dispRes;
DISPPARAMS dispparams;
dispparams.rgvarg[0].vt = VT_I2;
dispparams.rgvarg[0].iVal = number2;
dispparams.rgvarg[1].vt = VT_I2;
dispparams.rgvarg[1].iVal = number1;
dispparams.cArgs = 2;
dispparams.cNamedArgs = 0;
LPOLESTR GetFName[1] = {"Sum"};
hr = disp->GetIDsOfNames(IID_NULL, GetFName, 1, LOCALE_SYSTEM_DEFAULT,
functionID);
try {
if( SUCCEEDED(hr) ) {
hr = disp->Invoke( functionID[0], IID_NULL,
LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD,
&dispparams, &dispRes,
pExcepInfo, puArgErr );
}
}
catch(_com_error* e) {
}
sum1And2 = dispRes.iVal
}
SRComHelper
SRComHelper is class that encapsulates the above process and provides the user with a much simpler interface. The class provides a series of SetParam functions that a user can use to set the arguments of the function to call. A call to the Sum() function above using SRComHelper looks like:
int num1 = 3;
int num2 = 7;
int sum = 0;
int sumProduct = 0;
CSRComHelper comHelper( "SampleComDLL.ComTest" );
comHelper.CreateArray( 2 );
comHelper.SetParam( num1 );
comHelper.SetParam( num2 );
CComVariant retVal ("");
CString sFunctionName( "Sum" );
HRESULT hr = S_OK;
hr = comHelper.CallInvoke( sFunctionName.AllocSysString(),
retVal );
sum = retVal.iVal;
SRComHelper gets a little trickier to use when you have a return value and an argument passed as a pointer. Lets say we had a function
HRESULT SumAndMultiply(VARIANT num1, VARIANT num2,
[out] VARIANT* pProduct,
[out, retval] VARIANT* pSum).
SumAndMultiply
returns the sum of two numbers and the product. The call to Sum followed by a call to
SumAndMultiply
using SRComHelper
would be:
CSRComHelper comHelper( "SampleComDLL.ComTest" );
int num1 = 3;
int num2 = 7;
int sum = 0;
int sumProduct = 0;
comHelper.CreateArray( 2 );
comHelper.SetParam( num1 );
comHelper.SetParam( num2 );
CComVariant retVal ("");
CString sFunctionName( "Sum" );
HRESULT hr = S_OK;
hr = comHelper.CallInvoke( sFunctionName.AllocSysString(), retVal );
sum = retVal.iVal;
comHelper.Reset();
comHelper.CreateArray( 3 );
comHelper.SetParam( num1 );
comHelper.SetParam( num2 );
comHelper.SetParam( &sumProduct );
CComVariant retVal2;
sFunctionName = CString( "SumAndMultiply" );
hr = comHelper.CallInvoke( sFunctionName.AllocSysString(), retVal2 );
VARIANT res2;
res2 = comHelper.GetOutput( 2 );
SRComHelper
is not very elegant when getting the value of an argument passed as a pointer. One has to call the
GetOutput( int index )
function. It is, however, still better than having to write all the code behind the call to Invoke each time you have a new function.
I have not gone into much detail regarding the code of SRComHelper
as it is rather self explanatory (See code available for download). I use this class in a DLL with some other tools that I have built. The class can be directly part of your project or in a DLL. Hope it comes to some good use and saves you some time. Also, not all data types are included in the
SetParams
functions as I have been adding them as I need them. But the remaining should be easy to add. Also, please note that I treat each call to Invoke as a call to a Method.