Because not all IDL data types have mappings into all implementation languages, care should be exercised in making type choices at interface design time.
Although these modifiers do not affect marshalling, their presence is important when they are are mapped to language-level types.
int / long
Do not use data type int. Always use long.
char
Always qualify it explicitly with either signed or
unsigned.
Visual Basic
Interfaces designed for use with VB must not use
When binary data needs to be transmitted always use byte instead of
char.
char / wchar_t
wchar_t is preferred for single characters and strings, and
byte data type should be used for transmitting binary data. Therefore, the
char data type is of very limited use in COM
interfaces. Avoid using it if possible.
LPOLESTR / BSTR
When implementing interfaces in VB, interface inheritance should be avoided. VB cannot implement any interface that does not derive directly from IUnknown or IDispatch.
The [dual] attribute indicates that an interface is callable through v-table and through IDispatch. The
[dual] attribute also implies the [oleautomation] attribute, hence [dual] interfaces typically use the Universal Marshaler.
Whether a data type is signed or unsigned is irrelevant during marshalling. However, when IDL types are mapped to language-level types (C++ or VB), the presence of signed / unsigned modifiers is important.
Avoid the int data type and explicitly use long data type. Although the marshalling code treats an int as a long ,there is no guarantee that the C++ compiler will do the same.
char data type should be used with care. Always qualify it explicitly with either signed or unsigned. MIDL compiler treats unqualified char in IDL as unsigned char when generating C++ mappings. However, MIDL treats the unqualified char in IDL as signed char when generating the type library.
Interfaces designed for use with VB must not use the following data types (VB does not recognize them)
In C++, unsigned char and byte are equivalent. In IDL, they are not.
char data type is subject to format conversions on the wire, where as
byte is not. Therefore, when binary data needs to be transmitted always use
byte instead of char.
32-bit implementations use UNICODE to represent strings. In IDL, wchar_t is used to represent a UNICODE character, and when coupled with
[string] attribute, a wchar_t
data type represents a UNICODE string. Therefore, given that wchar_t
is preferred for single characters and strings, and that the byte data type should be used for
transmitting binary data, the char data type is of very limited use in COM interfaces. Avoid using
char data type if possible.
Visual Basic stores strings as UNICODE and has its own internal string type, string, which maps directly to
BSTR. Therefore, when developing interfaces for VB use the
BSTR type. In C++, LPOLESTR is typedefd as
BSTR which allows C++ to treat BSTR
and LPOLESTR as synonyms. However, in C++, the
BSTR data type is a pointer to
the first character in the string; the 4-byte count still exists and consequently,
BSTR should be managed using Win32 APIs:
void TakeLPOLESTR( LPOLESTR pwsz);
void TakeBSTR( BSTR bstr);
// pass a BSTR to LPOLESTR function
TakeLPOLESTR( bstrName);
This works correctly as long as bstrName does not have a NULL embedded in it. However,
void TakeLPOLESTR( LPOLESTR pwsz);
void TakeBSTR( BSTR bstr);
// pass an LPOLESTR to a BSTR function
TakeBSTR( pwszName);
TakeBSTR() function expects BSTR which always has a 4-byte count that precedes the string. If TakeBSTR() uses any of the Win32 API to manage BSTR, then we'll get a crash simply because pwszName does not contain the 4-byte count. Therefore, ensure that interface methods that use BSTR do not get passed an LPOLESTR. string.
[v1_enum] enum RGB // [v1_enum] explained below
{
RED,
// implicitly 0
GREEN = 0,
BLUE,
// implicitly 1
};
[v1_enum] enum RGB
{
RED,
// implicitly 0
GREEN = 0,
BLUE, // implicitly 1
};
[ . . . ]
library EnumLib
{
enum RGB; // enum defined outside the library block
}
[v1_enum] enum RGB // [v1_enum] explained below
{
RED,
// implicitly 0
GREEN = 0,
BLUE, // implicitly 1
};
[v1_enum] enum RGB
{
RED, // 0
GREEN, // 1
BLUE // 2
};
[ . . . ]
interface IColors : IUnknown
{
HRESULT SetColor( [in] enum RGB rgbColor );
}
A Visual Basic developer can pass any 32-bit value. A C++ developer can also pass an incorrect value using a cast. There are two ways to address this problem:
// IDL
HRESULT TakeAnObjRef( [in] IAtom* );
HRESULT ReturnAnObjRef( [out, retval] IAtom** ppAtom );
// CPP
IAtom* pAtom;
// Init pAtom . . .
pOb->TakeAnObjRef( pAtom );
. . .
IAtom* pAtom2 = NULL;
pObj->ReturnAnObjRef( &pAtom );
// IDL
HRESULT PersisteObject( [in] REFIID riid, [in, iid_is(riid)] IUnknown* pUnk );
// CPP Client
IPersistFile* pF;
IPersistXML* pX;
IPersistStorage* pS;
// pF, pS, pS initialized somewhere
PersistObject( __uuidof( pF), pF); // persist a File
PersistObject( __uuidof( pX), pX); // persist to XML
PersistObject( __uuidof( pS), pS); // persist to Storage
// CPP COM Server
HRESULT PersistObject( REFIID riid, IUnknown* pUnk)
{
if (riid == IID_IPersistFile)
IPersistFile* pF = (IPersistFile*)punk;
else if (riid == IID_IPersistXML)
IPersistXML* pX = (IPersistXML*)punk;
else if (riid == IID_IPersistStorage)
IPersistStorage* pS = (IPersistStorage*)punk;
...
}
Note that PersistObject never needs to call QueryInterface. Assume we did not use [iid_is()] attribute; we would have to QI the passed parameter which requires multiple round trips to the input object:
// IDL: Passing untyped object references without [iid_is]
HRESULT PersisteObject( [in] IUnknown* punk );
// CPP Client
IPersistFile* pF;
IPersistXML* pX;
IPersistStorage* pS;
// pF, pS, pS initialized somewhere
// Because all three interfaces are type-compatible with IUnknown each of them is a valid param to PersistObject()
PersistObject(pF); // persist a File
PersistObject(pX); // persist to XML
PersistObject(pS); // persist to Storage
// CPP COM Server
HRESULT PersistObject( REFIID riid, IUnknown* pUnk)
{
// QI pUnk for IPersistFile. If failed,
// QI pUnk for IPersistXML. If failed
// QI pUnk for IPersistStorage.
...
}
// IDL
HRESULT PersisteObject( [in] REFIID riid, [in, iid_is(riid)] void* pUnk);
// CPP
PersistObject( __uuidof(pF), (void*)pF);
// NO!
// IDL: Erroneous use of [oleautomation] and [iid_is]
[
uuid( . . . ),
object,
oleautomation
]
interface IVision : IUnknown
{
HRESULT TakeObjRef( [in] REFIID riid, [in, iid_is(riid)] IUnknown* punk);
}
Therefore, interfaces that use the Universal Marshaler, must either use strongly typed object references, or force the method implementation to use QueryInterface.
// IDL: Passing a struct by reference - VB and VC compatible
[
uuid(),
object,
...
]
interface IStruct : IUnknown
{
// MyPoint is only seen by IStruct. Other interfaces wont see it
struct MyPoint
{
long x; // almost used int instead of long! See rule 2
long y;
};
HRESULT TakePoint( [in] struct MyPoint* pPt );
};
Note that a struct definition can be placed either outside the interlace definition block or inside it. Placing a struct definition inside the interface definition means that only that interface will see that definition. This helps to keep related types close to each other. If the interface definition was later removed to a separate file, the struct definitions will automatically move with it.
// IDL: To pass a struct in a VARIANT, structs must be inside the library block
[ ... ]
library StructLib
{
[ ... ]
interface ITransform : IUnknown
{
HRESULT Transform( [in] VARIANT
vtFirst, [in] VARIANT vtFirst, [out, retval] VARIANT* pvtOut );
}
struct MyPoint
{
int nX;
int nY;
};
}
Passing a structure in a VARIANT from C++ is non-trivial and involves the use of IRecordInfo. Luckily, the system provides an implementation of IRecordInfo that can be retrieved using either GetRecordInfoFromTypeInfo or GetRecrodInfoFromGuids APIs .
// IDL: Be careful with typedef!
// Declare a structure
typdef struct MyPoint
{
int nX;
int nY;
} MYPOINT;
[ ... ]
library TypeDefLib
{
[ ... ]
interface ITrig : IUnknown
{
HRESULT AddPoint( [in] MYPOINT* pPt );
}
}
// .H: generated from above IDL
typedef struct tagMyPoint
// in IDL this is MyPoint
{
int nX;
int nY;
} MyPoint;
// in IDL, this is MYPOINT
HRESULT AddPoint([in] MyPoint* pPt);
// in IDL, MYPOINT
Therefore, the common C++ practice of defining type definitions for structures using tag<name> should be avoided. If it is important that the type library name and the C++ name be identical, and hence in VB be the same, consider using an identical name in both places
// IDL: Avoiding different struct name and struct alias
typedef struct MYPOINT
{
int nX;
int nY;
} MYPOINT;
// IDL: typedefs can have GUIDs. Avoid!
[
uuid(A7A4DDB4-84CC-11d5-8068-00D0B79F36F8 ),
public
]
typedef struct MyPoint
{
int nX;
int nY;
} MYPOINT;
There is a bug in the compiler that causes a DECLSPEC_UUID to be emitted in the wrong place. The workaround in complicated and unintuitive. Avoid adding GUIDs to typedefs.
// IDL: read/write, read-only, and write-only attributes
[ ... ]
interface IAtom : IUnknown
{
// Read/Write property
[propput] Name( [in] BSTR name );
[propget] Name( [out, retval] BSTR *pName );
// Read-only property - no [propput]
[propget] Weight( [out, retval] float *pfWeight );
}
// IDL: propput and propget with multiple params
[proput] HRESULT Name( [in] BSTR bstr1, [in] BSTR bstr2, [in] BSTR bstr3);
[propget] HRESULT Name([in] BSTR bstr1, [in] BSTR bstr2, [out, retval] BSTR *pbstr3);
// IDL: propputref
[ ... ]
interface IAtomeOfMoleculre : IUnknown
{
[propget] HRESULT Atom( [in] short nPosition,
[out, retval] IAtom** ppAtom);
[propput] HRESULT Atom( [in] short nPositon,
[in] IAtom* pAtom);
[propputref] HRESULT Atom( [in] short nPosition,
[in] IAtom* pAtom);
}
// VB: [propputref] requires Set
Dim atom1 as IAtom;
Set atom1 = Ob.Atom( 1 ) ' [propget]
Ob.Atom( 1 ) = atom1 ' [propput]
Set Ob.Atom(1) = atom1 ' [propoutref]
IDL supports interface inheritance. However, when/if implementing COM classes in Visual Basic, interface inheritance should be avoided because VB cannot implement any interface that does not derive directly from IUnknown or IDispatch. This is a limitation of VB runtime.
// IDL: A dispatch interface based on a union interface
// Interface 1: v-table
[ ... ]
interface IMechanialClock : IUnknown
{
[propput] HRESULT Hours( [in] long lHrs );
[propget] HRESULT Hours( [out, retval] long *plHrs );
};
// Interface 2: v-table
[ ... ]
interface IDigiatalClock : IUnknown
{
[propput] HRESULT Luminosity( [in] long lLum );
};
// Interface 3: union of all other v-table interfaces
[
...
hidden
]
interface _IHiddenUnion : IUnknown
{
// Methods of IMechanicalClock
[propput] HRESULT Hours( [in] long lHrs );
[propget] HRESULT Hours( [out, retval] long
// Methods of IDigiatal Clock
[propput] HRESULT Luminosity( [in] long lLum );
};
// Dispatch interface: All v-table interfaces are supported
[ ... ]
dispinterface _IClock
{
interface _IHiddenUnion;
};
// Note that the hidden interface does not appear in any coclass statement in IDL or in the
// QI implementation for the class
[ ... ]
coclass Clock
{
interface IMechanicalClock;
interface IDigitalClock;
interface _IClock;
};