from
http://progtutorials.tripod.com/COM.htm
- Why IDispatch
- IDispatch Methods
- A Simple IDispatch Implementation in Raw C++
- DISPPARAMS Structure
- VARIANT Structure
- COleVariant
- SAFEARRAY
- Dual Interface
- Dual Interface Implementation in Raw C++
1 Why IDispatch
As you can see from section ¡°Virtual Function Call Using vPtr and vtable¡± of chapter ¡°vPtr and vtable¡± of my C++ tutorial, an interface pointer returned by a COM component points to the vPtr of the interface in the coclass object¡¯s memory footprint.
If you want to make use of the vPtr, you have to use a language with a compilation facility, and the type of the interface vPtr must be known at compile time, so that the compiler can read the definition of the interface to find out the structure of its vtable, then generate some code and add it together with the original code that the programmer has written, to go through the vPtr and vtable and find the proper implementation of an interface method.
However, for script languages such as JavaScript and early versions of VB, there is no compilation process, and therefore the client can not generate code to utilize the vPtr and the vtable. To enable script languages to invoke itself, a COM component should support IDispatch interface. And of course, a script language that wants to invoke a COM component should know the type definition of IUnknown and IDispatch and have already generated the code to walk through their vtables.
Then, through a not-complicated mechanism, by using generic type VARIANT, the script language can find out through IDispatch all necessary information about any other custom interfaces the COM component supports, and call any of its methods.
2 IDispatch Methods
If a coclass wants to be used by script languages, it should support interface IDispatch. IDispatch interface has four methods:
- GetTypeInfoCount: check whether the server supports type information interface ITypeInfo.
- GetTypeInfo: retrieve an ITypeInfo pointer from the server, so as to access the type info contained in the type library.
The above two methods are normally not used, unless you want to design an application such as the OLE/COM Object Viewer.
- GetIDsOfNames: retrieve the DISPID (typedef of long) of one or several methods given their string names:
HRESULT GetIDsOfNames(
REFIID riid, // Reserved for future use; set to IID_NULL
OLECHAR ** rgszNames, // array of method names to be mapped
unsigned int cNames, // Count of the names to be mapped.
LCID lcid, // Locale context
DISPID * rgDispId); // disp ID for the method names
- Invoke: using the DISPID of a method, call this method:
HRESULT Invoke(
DISPID dispId,
REFIID riid, // reservered for future use, pass IID_NULL
LCID lcid, // Language ID (English, Arabic, etc.)
// Can be LOCALE_SYSTEM_DEFAULT
WORD wFlags, // DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT
// or DISPATCH_PROPERTYREF (when the property accepts a
// reference to an object).
DISPPARAMS * pDispParams, // a structure used to pass arguments
VARIANT * pVarResult, // return value
EXCEPINFO * pExcepInfo, // structure containing error info
UINT * puArgErr // index of the error argument in the VARIANTARG
// array in the DISPPARAMS structure
); Normally a server¡¯s IDispatch::GetIDsOfNames will return 0 for its first method, 1 for the second, 2 for the third, and so on. So you may even directly call Invoke without calling GetIDsOfNames, although it is not safe.
The limitation of Invoke is that you can only pass an array of variants. You can not pass custom types, unless you convert it to a BSTR and convert it back in the server method.
3 A Simple IDispatch Implementation in Raw C++
The coclass will simply inherit from IDispatch alone, and implement all its methods (three IUnknown methods and four IDispatch methods) as private methods which are only called internally by Invoke.
IUnknown methods are implemented as normal, in which QueryInterface only returns IUnknown and IDispatch interface pointer to clients:
STDMETHODIMP CAny::QueryInterface(REFID riid, void ** ppv)
{
if(riid == IID_IUnknown)
*ppv = (IUnknown *)this;
else if(riid == IID_IDispatch)
*ppv = (IDispatch *)this;
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
((IUnknown *)(*ppv))->AddRef();
return S_OK;
}
Method GetIDsOfNames:
STDMETHODIMP CAny::GetIDsOfNames(REFIID riid, OLECHAR ** rgszNames,
unsigned int cNames, LCID lcid, DISPID * rgDispId)
{
...
else if(_wcsicmp(*gszNames, L(¡°PrintEmployeeNames¡±)) == 0)
*rgDispId = 3;
...
}
Method Invoke:
STDMETHODIMP CAny::Invoke(DISPID dispId, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo,
UINT * puArgErr)
{
...
else if(dispId == 3)
{
PrintEmployeeNames();
}
...
}
4 DISPPARAMS Structure
This structure is used to pass a number of arguments to Invoke.
typedef struct
{
VARIANTARG * rgvarg; // Array of arguments.
DISPID * rgdispidNamedArgs; // Dispatch IDs of named arguments.
// NULL is no named arguments.
unsigned int cArgs; // Number of arguments.
unsigned int cNamedArgs; // Number of named arguments.
} DISPPARAMS;
5 VARIANT Structure
VARIANT is a data type supported by VB and Microsoft¡¯s universal IDL data type. Except for the reserverd members, it contains two members: one is a union of all universal IDL data types, the other is a VARTYPE (typedef of unsigned short) indicating the type of the union.
To initialize the VARIANT structure, call COM API function VariantInit.
typedef struct STRUCT tagVARIANT VARIANT;
typedef struct STRUCT tagVARIANT VARIANTARG;
typedef struct tagVARIANT
{
VARTYPE vt;
unsigned short wReserved1;
unsigned short wReserved2;
unsigned short wReserved3;
union
{
Byte bVal; // VT_UI1
Short iVal; // VT_I2
long lVal; // VT_I4
float fltVal; // VT_R4
double dblVal; // VT_R8
VARIANT_BOOL boolVal; // VT_BOOL
SCODE scode; // VT_ERROR
CY cyVal; // VT_CY
DATE date; // VT_DATE
BSTR bstrVal; // VT_BSTR
DECIMAL * pdecVal // VT_BYREF|VT_DECIMAL
IUnknown * punkVal; // VT_UNKNOWN
IDispatch * pdispVal; // VT_DISPATCH
SAFEARRAY * parray; // VT_ARRAY
Byte * pbVal; // VT_BYREF|VT_UI1
short * piVal; // VT_BYREF|VT_I2
long * plVal; // VT_BYREF|VT_I4
float * pfltVal; // VT_BYREF|VT_R4
double * pdblVal; // VT_BYREF|VT_R8
VARIANT_BOOL * pboolVal; // VT_BYREF|VT_BOOL
SCODE * pscode; // VT_BYREF|VT_ERROR
CY * pcyVal; // VT_BYREF|VT_CY
DATE * pdate; // VT_BYREF|VT_DATE
BSTR * pbstrVal; // VT_BYREF|VT_BSTR
IUnknown * * ppunkVal; // VT_BYREF|VT_UNKNOWN
IDispatch * * ppdispVal; // VT_BYREF|VT_DISPATCH
SAFEARRAY * * pparray; // VT_BYREF|VT_ARRAY
VARIANT * pvarVal; // VT_BYREF|VT_VARIANT
void * byref; // Generic ByRef
char cVal; // VT_I1
unsigned short uiVal; // VT_UI2
unsigned long ulVal; // VT_UI4
int intVal; // VT_INT
unsigned int uintVal; // VT_UINT
char * pcVal; // VT_BYREF|VT_I1
unsigned short * puiVal; // VT_BYREF|VT_UI2
unsigned long * pulVal; // VT_BYREF|VT_UI4
int * pintVal; // VT_BYREF|VT_INT
unsigned int * puintVal; // VT_BYREF|VT_UINT
}; // union
}; To pass a short number 123 into the server, you should use pass-by-value:
VARIANT v1;
VariantInit(&v1);
v1.vt = VT_I2;
v1.iVal = 123;
To get a long number from the server into a variable say long lStatus, you should set the ¡°VARTYPE vt¡± member to VT_BYREF | VT_I2, and set the ¡°long * plVal¡± member to &lStatus:
VARIANT v1;
VariantInit(&v1);
v1.vt = VT_BYREF | VT_I2;
v1.plVal = &lStatus;
To free up memory of VARIANT v1, use function VariantClear:
VariantClear(&v1);
To copy a VARAINT or change type of a VARAINT, use function VariantCopy and VariantChangeType.
ATL provides a wrapper class CComVariant.
6 COleVariant
COleVariant is wraps a VARIANT and can be treated or used exactly as a VARIANT. It's constructor takes a primitive type as argument. As other wrapper classes, it has a Attach and Detach method. Note that it does not have operator ++ overloaded.
LONG l = 25;
COleVariant Index(0L);
Index = l + 75L;
7 SAFEARRAY
Script languages prefer to use SAFEARRAYs. A SAFEARRAY can have multiple dimensions, each dimension may start from a non-zero number. It can also be locked by multiple clients.
typedef struct tagSAFEARRAY
{
USHORT cDims; // Number of dimensions of the array
USHORT fFeatures; // Used by COM API function to deallocate memory
ULONG cbElements; // Size of a single element
ULONG cLocks; // Number of locks imposed on the array
PVOID pvData; // Points to the actual data buffer
SAFEARRAYBOUND rgsabound[]; // Has cDims elements
} SAFEARRAY;
typedef struct tagSAFEARRAYBOUND
{
ULONG cElements; // Size of this dimension
LONG lLbound; // Lower bound (starting subscrip.) of this dimension
} SAFEARRAYBOUND; You do not directly operate on the structure, instead you call COM API functions:
SafeArrayCreate, SafeArrayGetDim, SafeArrayDestroy, SafeArrayGetElement, SafeArrayPutElement, SafeArrayGetUBound, SafeArrayGetLBound, SafeArrayAccessData, SafeArrayUnAccessData.
To create a SAFEARRAY of 5 long numbers in one dimension and fill them with 0 ~ 4:
SAFEARRAYBOUND bound[1] = {5, 0};
SAFEARRAY * psa = SafeArrayCreate(VT_I4, 1, bound);
for(int i = 0; i < 5; i++)
SafeArrayPutElements(psa, &i, &i); To passing a SAFEARRAY of type long through a interface method call, you only need to create a single VARIANT, set its member vt to VT_I4 | VT_ARRAY, and parray to the SAFEARRAY.
8 Dual Interface
A dual interface looks like
[
object,
uuid(D10A5C6C-2191-11D6-9867-A5AFD3E0F730),
dual,
helpstring("IAny Interface"),
pointer_default(unique)
]
interface IAny : IDispatch
{
[id(1), helpstring("method Method1")] HRESULT Method1(long a, short b);
[id(2), helpstring("method Method2")] HRESULT Method2(long a, short b);
[id(3), helpstring("method Method3")] HRESULT Method3(long a, short b);
};
As you can see, there are three differences between a dual interface and a normal interface:
- It has attribute [dual];
- It inherits not from IUnknown but IDispatch;
- Its methods are marked by ¡°id( )¡± attributes indicating the DISPIDs.
In the previous example, a coclass supports nothing but IDispatch. Therefore clients written in both early-binding and script languages have to go through IDispatch and suffer low speed and inconvenience. If a coclass supports a dual interface, a script client can still invoke all methods through IDispatch, while an early-binding client can make get a vPtr of the interface and make use of it to directly invoke the methods.
9 Dual Interface Implementation in Raw C++
Compared with the previous IDispatch-alone implementation, the implementation of a dual interface in raw C++ has only three differences:
- The coclass inherits from all the dual interfaces (such as IAny) instead of directly from IDispatch alone;
- The coclass implements the dual interface¡¯s methods as public methods instead of private, so that early-binding clients can directly call.
- QueryInterface not only return vPtrs of IDispatch and IUknown but also all the dual interfaces.
SeriousMoin v1 (koMoinMoin 1.0a4 Modified)