11.编写COM常用IDL指令和注意事项详解

来源:互联网 发布:淘宝卖家改地址 编辑:程序博客网 时间:2024/05/20 06:05

之前讲的COM都是手动编写的,上一节讲到借助MFC和下一节要讲到的ATL这些框架可以大大减少代码编写量,然而这还不够,还是太麻烦,因为COM遵循一套标准的规则,因此我们自然想到能不能使用一种描述语言,描述我们想要的COM形式和结构,然后由工具来做实际编写工作呢?

很幸运,微软实现了这个功能,实际上通过编写IDL的方法来编写COM也是微软推荐的做法,这样做好处在于:

1.可以减少代码编写工作量

2.对于底层尤其是在编写进程外组件时做到透明,减小编写难度

3.生成.tlb类型库,包含了相关二进制的符号信息,从而允许其他语言来动态调用


IDL编译有两种方法:

1.COM使用Win32 SDK包含的MIDL.exe来编译IDL,此时需要注意相关路径的引用问题,最简单的就是$WindowsSDK/bin中的midl.exe/midlc.exe和要编译的idl拷贝到$WindowsSDK/include目录中,然后命令行执行【midl.exe idl文件名】

2.在VS2008中直接编写IDL,编译时会自动调用MIDL生成对应文件

比如,对于编写好的DOG.IDL,编译后生成文件如下


一个典型的IDL结构如下,它包含了常见的IDL定义

cpp_quote("//------generate from idl----------")  //生成的C++代码中添加注释import "oaidl.idl";import "unknwn.idl";//接口定义[object,                                       //所定义的接口是一个COM接口uuid(7D796BB3-E479-42C9-99F9-FC2189CF8E78),   //相应的接口IID   helpstring("IDog 接口"),                      //对应字符串会放入类型库    pointer_default(unique)                       //默认的次级指针类型为unique]interface IDog : IUnknown{    [helpstring("主人性别")]    typedef enum tagGENDER     {        Female,         Male    }GENDER;        [helpstring("主人信息")]    typedef struct tagHumanInfo    {        long    nHumanId;        GENDER  eGender;    } HUMAN;        [helpstring("狗狗信息")]    typedef struct tagDogInfo    {        long nDogId;        [unique] HUMAN *pOwner;    } DOG;                [helpstring("获取接口IOperate")]     HRESULT GetInterface([in] REFIID nClsid,                        //in表示输入参数                          [out, iid_is(nClsid)] void** pInterface);  //out表示输出参数,iid_is指明对应的接口IID[helpstring("方法SayHello")]     HRESULT SayHello([in,string] WCHAR* szWord);                    //string指明输入参数是string类型,方便传递时动态计算长度    [helpstring("方法SayHi")]     HRESULT SayHi([in,string] BSTR szWord);    [helpstring("方法GetChildAges")]     HRESULT GetChilds([out] SAFEARRAY(long) *pArrAge);              //注意SAFEARRAY需要指明成员类型,作为返回值必须为指针    [helpstring("方法GetProperty")]     HRESULT GetGetProperty([in,string] BSTR szPropKey,                            [out,retval] VARIANT* pVal);             //VARIANT可变参数,作为返回值必须为指针        [helpstring("方法GetAge")]     HRESULT GetAge([out, retval] long* pVal);                       //retval经常和out混用,表示返回值    [helpstring("方法TranslateWord")]     HRESULT TranslateWord([in, string] BSTR szInput,                           [out,string,retval] BSTR* pszOutput);     //BSTR作为返回值必须为指针        [propget, helpstring("属性-Get")]                               //propget和propput 分别对应属性的get和set    HRESULT Weight([out, retval] long* pVal);    [propput, helpstring("属性-Put")]     HRESULT Weight([in] long nVal);        [helpstring("传递指定量的狗骨头,返回实际吃的量-EatBones")]     HRESULT EatBones([in] long nSize,                      [out] long *pActual,                      [out, size_is(nSize), length_is(*pActual)] long* pData);};//类型库定义,只能包含一个,所有的类对象coclass都必须在其中定义[uuid(63CD81C0-FD49-4153-A6CF-56BC8BA97935),version(1.0),                                                    //类型库版本号    //lcid(9),                                                       //定义库的地域id,9=英文helpstring("CAnimalObject Type Library")]library AtlBaseComLib{importlib("stdole2.tlb");[uuid(A0A0C1F6-B5F4-42D1-80A2-C4D47B99DC2D),helpstring("AnimalObject 组件对象")]coclass CAnimalObject                                             //coclass指明类对象{[default] interface IDog;                                     //默认获得的接口指针,一个类对象只能定义一个};};

已经写的注释不再详细说明,主要讲COM IDL编写中的注意事项:


1.IDL中的指针

考虑如下一个常见IDL定义

HRESULT  Method([in] short* p, [in] short* p1)

a) p=null的时候,代理存根如何传递呢?很显然按照原始数据是没法传递的,这种指针成为ref类型指针(引用指针)

b) 如果非要允许传递null指针,可以指明指针为unique类型(单值指针),这种指针在列集传递时使用额外数据标记这是null指针

c) 再考虑如下,如果p=p1,按照通常处理过程,p和p1是指向不同对象的,如果是表示可能指明同一数据,需要指明为ptr类型(全指针),这时候列集器会检查其他ptr指针是否有重复,一般不使用

d) COM中返回类型都是HRESULT,如果要实现

long GetAge()这种函数,在这里可以通过标记指针为retval类型(返回类型),IDL定义如下

HRESULT GetAge([out,retval] long* pVal)

在C++中映射为如下函数

HRESULT _stdcall GetAge(long* pVal); 

HRESULT标记调用状态

在Java等语言中映射为

long GetAge(); 

HRESULT被自动转为Java异常

COM中一般是调用者分配内存,由组件填充实际内容,因此要求指针类型的[out]参数必须为ref类型。


考虑如下IDL

typedef struct tagDogInfo{    long nDogId;    HUMAN *pOwner;} DOG; HRESULT GetDogInfo([out, retval] DOG* pDogInfo)
这里pDogInfo称为顶级指针,定义在结构体中的指针pOwner称为内嵌指针,内嵌指针可以无限嵌套。

默认顶级指针为ref类型,可使用pointer_default指明默认的内嵌指针类型。


这里调用分配一个结构体,指针pDogInfo传给组件,组件实现中需要分配HUMAN实际内存,填充指针pOwner。但是不同的模块的运行库不一样,所以传统的内存分配无法实现跨模块调用,这里一般使用CoTaskMemAlloc/CoTaskMemReAlloc/CoTaskMemFree,在组件分配内存,在客户释放。


详细的指针和内存请参考《COM本质论》最后一章。


2.IDL中的数组

常见数组是固定数组,可IDL定义如下

HRESULT Method1([in] short arr[8]);

对应传递结构如下



这种只能传递指定长度的数组,为了传递可变长度的数组,IDL引入适应性数组

HRESULT Method2([in] long nSize, [in, size_is(nSize)] short *pArr)HRESULT Method2([in] long nSize, [in, max_is(nSize-1)] short *pArr)

size_is和max_is等价,前者指明数组长度,后者指明数组最大索引,缓冲区接收到的数据就是传给方法的参数数组数据,对应传递结构如下



大多数时候我们传递给组件一个数组,让他填充数组内容,如下

HRESULT Method3([in] long nSize, [out, size_is(nSize)] short *pArr)
如果需要我们传递的数组长度为8,但是实际回传的数据只有2个,这样远程传输时会增加不必要的带宽,为此IDL引入可变数组,可以指明实际传递的数据长度,此时如下定义

HRESULT Method4([in, length_is(2)] long arr[8]);HRESULT Method4([in, first_is(2), length_is(5)] long arr[8]);HRESULT Method4([in, first_is(2), last_is(6)] long arr[8]);
这里length_is指明数组实际传递的数据长度,first_is和last_is分别标记当前传递的数据的起始和结束索引,对应的传递结构为

这里有一个问题——接收数据的缓冲区的数据需要重组才是最后数组实际内容,会浪费一块内存


实际中,为了优化传输,最好的是size_is和length_is结合使用,此时称为适应性可变数组(开放数组)

常用IDL定义如下

HRESULT Method5([in] long nSize,                 [out] long *pActual,                 [out, size_is(nSize), length_is(*pActual)] long* pData);HRESULT Method5([in] long nSize,                 [in, out] long *pActual,                 [in, out, size_is(nSize), length_is(*pActual)] long* pData);
对应的传递结构为


如果不想怎么麻烦,或者是和没有数组定义的语言打交道,可以直接使用SAFEARRAY类型

详细数组类型使用请参考《COM本质论》最后一章。

3.IDL中的字符串

和常规数组数据不同,字符串数据是变长的,无法在列集时指明固定长度,所以需要指明string属性,列集器会自动根据字符串长度计算需要列集的数据长度。

在C++中可以使用WCHAR*和BSTR,但是如果接口是提供给Java等语言使用的,必须使用BSTR


4.IDL指明属性

为了简化属性的获取和设置,COM提供了propget和propput属性,对应生成的函数会自动加上 get_和set_前缀


关于自动化接口相关idl指令稍后再介绍

详细的idl属性介绍可参考微软文档


演示idl和生成文件下载链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

原创粉丝点击