05.IDL
5. IDL
5.1. Interface Definition Language (IDL)
With a server created purely in C++, without using IDL compiler, a client have to make use of the two header files which defines the interfaces and the guids. Because both these two files are in C++ syntax, clients written in other languages such as VB or Java can not invoke it. So such a COM server is not language independent.
Therefore we need a language-independent way to describe the interfaces of a COM server. Microsoft IDL (MIDL) is used for this purpose.
An IDL file does not contain any implementation information. Therefore you can not define a class in IDL with implemented methods. You can only define interfaces in IDL and implement them in a specific language such as VC or VB. However, you can define data structures which doesn't contain any implementation.
5.2. Type Library
When you define a COM server in an IDL file say Any.idl and send it to VC's integrated MIDL compiler, it will generate a C++ header file Any.h (which serves as interface definition), and a guid definition file Any_i.c for C++ clients, and a type library file Any.tlb containing all information of a COM server, which is in a language-independent binary format, therefore can be understood by different languages such as VC, VB, BJ++, etc.
When a C++ client wants to make use of type library, he can use #import to import the type library. When a VB client wants to invoke a COM server and design time, he can select from the list of registered type libraries from the "Project" | "Preferences" dialog. If one type library is not registered, he can also browse to select one.
To register a type library, under entry HKEY_CLASSES_ROOT\TypeLib, you should create an entry of the guid of the type library, under which you specify the help string and directory of this type library. This is why VB can show you a list of registered type libraries. You should add a TypeLib entry under the HKEY_CLASSES_ROOT\CLSID of each coclasses contained in this type library, so that SCM can find the type library guid from a coclass. All these things can be done by ATL.
In C++, binary information defined by IDL is stored in both the .dll file and the .tlb file. In Visual Basic it is only stored in DLL file. A EXE server will have its info in the EXE file.
5.3. IDL Compatible Data Types
If you only use the following IDL data types, your interfaces can be used by all COM-enabled interfaces:
<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /?>
VARIANT_BOOL, double, float, long, short, BSTR, DATE, IDispatch*, IUnknown*, VARIANT, CY/CURRENCY, SAFEARRAY
However, when you use other types such as ULONG or HRESULT in your IDL, they are also OK, because they have been defined by typedef in "wtypes.idl" as IDL data types. You do not need to import "wtypes.idl", because it will be imported by "oaidl.idl".
5.4. Defining Coclasses, Interfaces and Libraries in IDL
IDL file of an empty COM server looks like
import "oaidl.idl";
[
uuid(7013DBBD-A9FC-11D5-9866-E39F283C9930),
version(1.0),
helpstring("Test 1.0 Type Library")
]
library TESTLib
{
importlib("stdole32.tlb");
};
"oaidl.idl" is the only IDL file you must import in your IDL file. Type library "stdole32.tlb" is the standard OLE type library, which must be imported by any type library.
¨ Adding a coclass
To add a new coclass, add the following coclass declaration lines in the library block:
[
uuid(7013DBCD-A9FC-11D5-9866-E39F283C9930),
helpstring("MyCom Class")
]
coclass MyCom
{
};
¨ Adding an interface and its methods
We can use AppWizard to add coclasses and their default interfaces, but we have to add additional interfaces ourselves. For each interface, we add its declaration in front of the library block. Attribute "object" indicates that this is a COM interface not a DCE IDL interface.
[
object,
uuid(7013DBCB-A9FC-11D5-9866-E39F283C9930),
helpstring("ICOMClass1 Interface")
]
interface IMyInterf : IUnknown
{
[helpstring("method GetBalance")]
HRESULT GetBalance([in] int aa, [out] LONG * bb);
[helpstring("method Deposit")]
HRESULT Deposit([in] int aa);
};
Then we add the interface name in the coclass definition:
[
uuid(7013DBCD-A9FC-11D5-9866-E39F283C9930),
helpstring("MyCom Class")
]
coclass MyCom
{
[default] interface IMyInterf;
};
the interface with [default] attribute is the default interface of the coclass.
An interface should belong to a coclass. A standard-alone interface which does not belong to any coclass is alowed by MIDL compiler and can be implemented manually, but it is not shown when using Implement Interface Wizard.
Then we should let the coclass inherit from the interface, and in ATL we should add a COM map entry for the interface.
5.5. Defining Data Structures in IDL
If your server needs to use a custom type (a class or a structure) internally, you can define the new type in a normal header file (plus cpp file if necessary) and #include the header file wherever it is used.
However, if this custom type is used as an argument in a method of an interface which needs to be transfered by the marshaller, then you should define the custom type in your main IDL file, or in a separate IDL file and import it in your main IDL file.
Then, when MIDL compiler compiles the main IDL file, the header file it generates will contain the definition of the custom type. If the custom type is defined in a separate IDL file, then you have to compile the separate IDL file first, then the main IDL file. Then the header file generated from the main IDL file will automatically include the header file generated from the separate IDL file.
Because the main IDL file either contains the definition of the custom type or imports its definition, the client who invokes this server can directly use the custom type.
If the custom type contains any variable-length array member, you must put size_is attribute in front of the variable-length data member to inform the marshaller of the member's size:
typedef struct {
short sSize;
[size_is(sSize)] short sArray[];
} Group;
Also notice that the array member is represented by array syntax "short sArray[ ]" not pointer syntax "short * pShort". For some reason the latter one doesn't work.
5.6. Defining Enums in IDL
[uuid(442F....804), v1-enum]
typedef enum {
HATCH = 0,
SOLID = 1,
POLKADOT
} FILLTYPE;
Attribute v1-enum indicates that these enums are 32-bit, which is more efficient in marshalling.
5.7. MkTypLib Compatible Option
Each VC project has a "MkTypLib Compatible" option. This option is found under "Project | Settings | MIDL". This is by default set, which means that all IDL files must conform to older ODL syntax. You should turn it off. In a project generated by ATL COM AppWizard, it is turned off.
5.8. MIDL Generated Files
Suppose the IDL file name is Shapes.idl, the files generated by MIDL are:
1. Shapes.h Interface definition
2. Shapes.tlb binary type library
3. Shapes_i.c definition of all guids
4. Shapes_p.c and dlldate.c used to build proxy and stub DLLs for remote accessing
5.9. Importing Type Library
In C++, to invoke a server or implement an interface defined in it through its type library is to #import the type library wherever you need it – in the *.h or *.cpp file. When you #import a type library say Shapes.tlb, the compiler will generate two C++ files: Shapes.tlh and Shapes.tli, which contains definitions for the interfaces, guids and and smart pointers. Both files are placed in the output directory e.g. "debug", and then read and compiled by the compiler as if they are included. So after you compile the project, you can actually comment out the #import line and #include the tlh file.
A typical #import line looks like:
#import "D:\icp97\pc\core\code\ICPCore.tlb" \
no_namespace, named_guids, raw_native_types, raw_interfaces_only
¨ no_namespace
If you do not put the "no_namespace" attribute after the #import, most sections in the generated tlh file will be enclosed in a namespace block, which is named after the type library name. This is useful to prevent name conflicts between names used in your client program and names imported from the COM server. To use the names in the COM server, you should put the following declaration immediately after the #import line (the name of the type library is ICPCore.tlb):
using namespace ICPCore;
By putting the "no_namespace" attribute, the namespace declaration in the generated tlh file will be supressed. Therefore all names in the tlh file are in the same namespace as your client program. You can directly quote them.
¨ named_guids
Attribute "named_guids" instructs the compiler to define the guids contained in the tlh file with CLSID_, IID_ and LIB_ prefix.
If a type library includes references to types defined in other type libraries, then the .TLH file will include comments of the following sort:
// Cross-referenced type libraries:
//
// #import "c:\path\typelib0.tlb"
¨ raw_native_types
By default, the high-level error-handling methods use the COM support classes _bstr_t and _variant_t in place of the BSTR and VARIANT data types and raw COM interface pointers. These classes encapsulate the details of allocating and deallocating memory storage for these data types, and greatly simplify type casting and conversion operations.
This attribute is used to disable the use of these COM support classes in the high-level wrapper functions, and force the use of low-level data types instead.
¨ raw_interfaces_only
This attribute suppresses the generation of error-handling wrapper functions and __declspec(property) declarations that use those wrapper functions.
It also causes the default prefix used in naming the non-property functions to be removed. Normally, the prefix is raw_. E.g., if a method of the original interface is called MyMethod in the IDL and type library, then in the *.tlh file generated by the #import from the type library, it will become raw_MyMethod. If this attribute is specified, the function names are directly from the type library.
This attribute allows you to expose only the low-level contents of the type library.
5.10. Smart Pointers
When VC compiler sees preprocessor directive #import *.tlb, it reads the type definition in the type library and generates smart pointer classes from IDL files, to wrap low-level functions calls such as CoCreateInstance, Release, InterfaceSupportsErrorInfo, GetErrorInfo, GetDescription, etc.. For interface IAccount and IDisplay, there will be smart pointer class IAccountPtr and IDisplayPtr.
These smart pointers can automatically acquire interface reference with CoCreateInstance. You do not need to call QueryInterface to acquire a new interface from the original one. You can just assign the original interface smart pointer to the new one. AddRef and Release are also called automatically.
When a when a smart pointer detects an error from the returned HRESULT, it will throw a _com_error exception. You can call its ErrorMessage method, which calls API function FormatMessage to return an explanation of the HRESULT, such as "Invalid pointer". To retrieve the extra error information set with IErrorInfo objects, call _com_error's Description method.
A smart pointer also supports IDL attribute [out, retval].
Because smart pointer classes are higher level than raw interface pointers, it is more error prone. I have once run into a job which can be done by using raw interface pointers but not smart pointer classes.
To use smart pointer for interface IAccount and IDisplay of coclass Account in server BankATL:
try{
IAccountPtr pAccount("BankATL.Account.1");
/* or you can say
IAccountPtr pAccount;
pAccount.CreateInstance(CLSID_Account);
or
IAccount pAccount(__uuidof(CAccount));
*/
int balance;
pAccount->GetBalance(&balance);
pAccount->Deposit(23);
pAccount->GetBalance(&balance);
IDisplayPtr pDisplay = pAccount;
pDisplay->Show();
}
catch(_com_error &ex)
{
MessageBox(ex.ErrorMessage());
}
When you pass the ProgID to a smart pointer's constructor, a CoCreateInstance call has already been done. Therefore, you should be cautious when you make a smart pointer a data member or especially a global variable – AfxOleInit must be called before the smart pointer is created.
5.11. [retval] Attribute
Parameter marked with "[ out, retval ]" can be used by smart pointers or other languages such as VB as logical return type. Suppose method Add of interface IMath implemented by coclass CMath is defined as
HRESULT Add([in] long x, [in] long y, [out, retval] long * z);
You can say
IMathPtr sp (__uuidof(CMath));
long result = sp->Add(111, 222);
/* which is equal to
long result;
sp->Add(111, 222, &result);
*/
In VB:
Dim ans as Integer
ans = c.Add(111, 222)
5.12. [oleautomation] Compatible Data Types
Originally COM specifications was generated to be used only for C/C++. Some of C/C++’s types doesn’t map to other languages such as VB. If your COM component has an interface method with such a data type as parameter, other languages may not be able to call this method.
To solve this problem, Microsoft developed a set of universal IDL data types that can map to all languages. It is based on VB’s data type “Variant”. They are:
|
Universal IDL Type |
VB Mapping |
J++ Mapping |
C/C++ Mapping |
|
VARIANT_BOOL |
Boolean |
boolean |
VARIANT_BOOL |
|
double |
Double |
double |
double |
|
float |
Single |
float |
float |
|
long |
Long |
long |
long |
|
short |
Integer |
short |
short |
|
BSTR |
String |
java.lang.String |
BSTR |
|
DATE |
Date |
double |
DATE |
|
IDispatch * |
Object |
java.lang.Object |
IDispatch * |
|
IUnknown * |
Interface reference |
com.ms.com.IUnknown |
IUnknown * |
|
VARIANT |
Variant |
com.ms.com.Variant |
VARIANT |
|
CY/CURRENCY |
Currency |
long |
CY/CURRENCY |
|
SAFEARRAY |
Variant |
com.ms.com.SafeArray |
SAFEARRAY |
If your COM component’s interface methods all use the above universal data types, then the component can be invoked by client programs written in all languages.
In such a case, you’d better put attribute [oleautomation] in the interface declaration:
[
object,
uuid(C39A9A9A-1E04-11D6-9867-F05CFEE1FC30),
oleautomation,
helpstring("IAny Interface"),
pointer_default(unique)
]
interface IAny : IUnknown
{
[helpstring("method Hi")] HRESULT Hi([in] A a);
};
With this attribute, MIDL compiler will check that all interface methods use only universal data types. If not, e.g. a method has a parameter of a custom type say CEmployee * pEmployee, it will generate a warning message like “interface does not conform to [oleautomation] attribute”.
If you use dispinterface or dual interface than you have to stick to this standard.
If your COM component comply to the [oleautomation] standard, it can make use of COM’s automatic data marshalling DLL oleaut32.dll. Otherwise you have to build and register your own stub/proxy DLL server (MIDL will generate the make file for you so it is not difficult either).
History
Last edited on 04/09/2007 16:34 by dragona79
Comments (0)