The main points in this section were borrowed from "Understanding Interface Definition Language : A Developer's Survival Guide". Available in MSDN.
Whenever thinking about marshaling, the ultimate objective is to copy data from the address space of one side (client or server) to the address space of the other side (server or client.) Whatever exists in the address space of one side at the time of the call must be replicated in the address space of the other side. Therefore,
An IDL is used to accomplish the following
In the following note that Automation Compatible means anything that can go into a
VARIANT
File Name | Purpose |
dlldata.c | proxy/stub DLL entry points |
<IDLFileName>_p.c | proxy/stub interface declarations |
<IDLFileName>.h | interface header file (pure virtual methods) |
<IDLFileName>_i.c | IID, CLSID, LIBID definitions |
- Type library
File Name Purpose <IDLFileName>.tlb Type library
IDL Include | Description |
wtypes.idl | Fundamental definitions file. |
unknwn.idl | IUnknown & IClassFactory. Imports wtypes.idl |
objidl.idl | Fundamental COM support interface classes. imports unknwn.idl |
oaidl.idl | Automation interfaces. import objidl.idl |
oleidl.idl | OLE interfaces. imports objidl.idl |
ocidl.idl | ActiveX control interfaces. imports objidl.idl |
(other IDLs are ulrmon.idl, hlink.idl, docobj.idl) |
SampleIDL.IDL
/* The following import statements will cause SampleIDL.h to contain
#include "oaidl.h"
#include "ocidl.h"
*/
import "oaidl.idl";
import "ocidl.idl";
/* This section will only generate proxy stub code since
it is outside the
library section */
[
/* object tells MIDL to produce files for a COM interface. Without
it, MIDL generates files necessary for an RPC interface */
object,
/* uuid() is required to generate proxy/stub code. */
uuid(FBBF2DEF-1391-11D4-8ED9-000000000000),
/* TO DO */
dual,
helpstring("ITestIDL Interface"),
/* pointer_default(ref | ptr | unique) specifies the default pointer
attribute for all pointers except top level pointers. This includes
pointers returned by functions, pointers within a struct, pointer to a
pointer, etc. For top level pointers, the default is 'ref' which means
that it cannot be NULL and it should always point to a valid address. */
pointer_default(unique)
/* local attribute prevents the MIDL from generating proxy/stub
(networking) code for this interface. This also frees the interface
functions from returning HRESULT since the interface methods can only
be invoked in-proc.*/
// local
]
interface ITestIDL : IDispatch
{
/* [in] and [out]
Marshaling data across network is expensive. To optimize, [in] tells
the marshaler that its variable is only marshaled (moved) when traffic
is from client to server. [out] is used to marshal data from server to
client.
An [out] parameter MUST be a pointer. Why? For an [out] param, the
server allocates data and moves it back to client space. But where in
the client space should the server move the data to? Using a pointer for
[out] params tells the server where the resulting data is to be placed.
This means that the pointer on the client side must represent valid
address. And since this is a top level pointer, the default is
ref */
[ id(1), helpstring("method SetGetInteger") ]
HRESULT SetGetInteger( [in] long lNum, [out, retval] long *plNum
);
/* [ref] pointers are reference pointers that should always point to a
valid address. This is the default for top level pointers. */
[ id(2), helpstring("method PassPtrWithRefAttribute")
]
HRESULT PassPtrWithRefAttribute( [in, ref] const long* plVal
);
/* [unique] pointers can be passed as null. This should be the attribute
for pointers within a struct or to pointers returned by a function */
[ id(3), helpstring("method PassPtrWithUniqueAttribute")
]
HRESULT PassPtrWithUniqueAttribute( [in, unique] long *plValue
);
/* [full] pointers are used to emulate duplication: A full pointer
performs duplicate pointer detection and will make sure that duplicate
pointers are unmarshaled as duplicate pointers pointing to one memory
location instead of two. Break into PassPtrWithFullAttribute()
function
in the server code and watch the addresses */
[ id(4), helpstring("method PassPtrWithFullAttribute") ]
HRESULT PassPtrWithFullAttribute( [in,ptr] long *plVal1,
[in,ptr] long *plVal2 );
/* [string] lets the marshaler pass the data the pointer is pointing to
until it hits a NULL. Two important points here:
1. Without any attribute for a pointer, pointers are assumed to point
to a single instance of a data type, ie, marshaler will only marshal
one data item.
2. When using [string] the string type should be OLECHAR and not CHAR
*/
[ id(5), helpstring("method PassString") ]
HRESULT PassString( [in, string] OLECHAR *pwszName );
/* Passing an array. Again we need to indicate how much to marshal. In
fact, using size_is would accomplish the same thing as
[string]
Note that max_is() is equivalent to size_is() except that max_is()
specifies the maximum index of the array */
[ id(6), helpstring("method PassArray") ]
HRESULT PassArray( [in] long lSize, [in, size_is(lSize)] long *plArray
);
/* iid_is tells the marshaler
that we are passing an interface pointer whose
IID is indicated by the parameter in iid_is() */
[ id(7), helpstring("method GetInterfacePointer")
]
HRESULT GetInterfacePointer( [in] REFIID riid, [out, iid_is(riid)] void** ppv
);
};
/* The following is a dispinterface:
uuid(CC7954B0-14B3-11d4-8EDD-000000000000)
]
dispinterface ITestIDL2
{
properties:
[id(1)] long lAttribute;
methods:
};
*/
/* Everything that is inside the library statement will be added to
the type library. A tlb can contains only the following five
typedefs
modules - check out the [module] IDL keyword
interfaces
dispinterfaces
coclasses */
[
uuid(FBBF2DE3-1391-11D4-8ED9-000000000000),
version(1.0),
helpstring("IDL 1.0 Type Library")
]
library SampleIDLLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
/* If ITestIDL2 was a uncommented below, it will be added to the type
library. Upon registering the type library (when the server
code calls
CComModule::RegisterServer() function), the dispinterface will be added
to the \interfaces registry key and designated to
use oleaut32.dll
(the universal marshaler) as its marshaler.*/
//dispinterface ITestIDL2;
/* Coclasses are not registered by the type library.
*/
[
uuid(FBBF2DF0-1391-11D4-8ED9-000000000000),
helpstring("TestIDL Class")
]
coclass TestIDL
{
/* ITestIDL will be registered. Because its is [dual], it will be
assigned the universal marshaler.*/
[default] interface ITestIDL;
};
};
#import "..\SampleIDL.tlb"
using namespace SampleIDLLib;
// Thread function to introduce threading issues
DWORD WINAPI ThreadProc( LPVOID );
// Helper functions
void TestInOutParams();
void TestPointerParam();
void TestStringsAndArrays();
int main(int argc, char* argv[])
{
/* Run the objects on a separate thread than that of the main program.
This should serve to introudce marshaling issues */
DWORD dwID;
HANDLE hThread = CreateThread( NULL, 0, ThreadProc, NULL, 0, &dwID );
if (NULL != hThread)
{
WaitForSingleObject( hThread, INFINITE);
}
CloseHandle( hThread );
return 0;
}
DWORD WINAPI ThreadProc( LPVOID )
{
/* Recall that CoInitialize() must be called on the thread where the object
is running, and not on the primary thread created by the application
*/
CoInitialize( NULL );
{
TestInOutParams();
TestPointerParam();
TestStringsAndArrays();
}
CoUninitialize();
return 0;
}
void TestInOutParams()
{
// Create test object
ITestIDLPtr gspIDLTest;
gspIDLTest.CreateInstance( __uuidof( TestIDL ) );
/* 'lNum' is an [out] param. It is allocated in the server and freed
in the client. Note the an [out] param tells the server where to place
its data which can be a simple integer or a pointer to an array of
chars (ie, string) Therefore, we must always pass a valid
client
address (pointer) to the server. The server allocates its
data
and puts it in the address passed in by the client. */
long lNum;
gspIDLTest->raw_SetGetInteger( 10, &lNum );
/* This is INCORRECT since we're
passing an invalid address to the server.
The server has no place to copy its allocated data.
long *plNum;
spIDLTest->raw_SetGetInteger( 20, plNum );
delete plNum;
*/
}
void TestPointerParam()
{
// Create object
ITestIDLPtr gspIDLTest;
gspIDLTest.CreateInstance( __uuidof( TestIDL ) );
/* PassPtrWithRefAttribute() has [in, ref] for its input parameter. This
means the input param is a reference parameter which MUST always contain
a valid address. If it is null, the marshaling code will return an error.
TODO:
This didn't work for me! */
long *plNum1;
gspIDLTest->PassPtrWithRefAttribute( plNum1 );
/* PassPtrWithUniqueAttribute() has [in, unique] for its input parameter.
This means the input pointer can be NULL.*/
long *plNum2;
gspIDLTest->PassPtrWithUniqueAttribute( plNum2 );
/* PassPtrWithFullAttribute() has [in,ptr] for its input param. This
means the input pointer is treated as a full pointer. Note that we are
passing the address of the same pointer. On the server side, unless we
use the [ptr] IDL attribute, the server will allocate two memory blocks
for the same variable. The [ptr] attribute detected pointer duplications
and makes sure that they are marshaled as a duplicate pointer.*/
long lVal = 10;
gspIDLTest->PassPtrWithFullAttribute( &lVal, &lVal );
}
void TestStringsAndArrays()
{
// Create object
ITestIDLPtr gspIDLTest;
gspIDLTest.CreateInstance( __uuidof( TestIDL ) );
/* Passing a string using [string] attribute as opposed to using a BSTR.
[string] tells the marshaler to compute the length of data the pointer is
pointing to */
LPWSTR lpszString = L"hello there";
gspIDLTest->PassString( lpszString );
/* Passing an array using size_is() attribute */
long lArr[2] = {1,2};
gspIDLTest->PassArray( 2, lArr );
/* Testing iid_is() attribute */
ITestIDL *pIDL;
IID IID_TestIDL = __uuidof(ITestIDL);
HRESULT hr = gspIDLTest->raw_GetInterfacePointer( &IID_TestIDL, (void**) &pIDL);
pIDL->Release();
}