Data Types

Summary

IDL Data TYPES SUMMARY

Because not all IDL data types have mappings into all implementation languages, care should be exercised in making type choices at interface design time.

Signed / Unsigned

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

unsigned char/ byte

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


Enumerated Types

Object References

Structures

Typedef

Properties

Interface Inheritance

When implementing interfaces in VB, interface inheritance should be avoided. VB cannot implement any interface that does not derive directly from IUnknown or IDispatch.

Dispatch Interfaces

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.

DETAILS

SIGNED / UNSIGNED

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.

INT / LONG

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

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.

VISUAL BASIC

Interfaces designed for use with VB must not use the following data types (VB does not recognize them)

UNSIGNED CHAR / BYTE

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.

CHAR / WCHAR_T

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.

LPOLESTR / BSTR

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. 

ENUMERATED TYPES

[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:

  1. If interception code is used between client and object, use the [range] attribute. However, note that it can only be used if all of the following are true,
  2. Because of those restrictions, the second and safest approach is to perform range checking at run-time inside the function call.

OBJECT REFERENCES

// 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.

STRUCTURES

// 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 .

TYPEDEFS

// 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.

PROPERTIES

// 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]

INTERFACE INHERITANCE

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.

DISPATCH INTERFACES

// 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;
};