9.COM中特殊类型和相关函数

来源:互联网 发布:cydia软件源微信抢红包 编辑:程序博客网 时间:2024/06/07 16:16

前面的讲解,基本上建立了对COM的基础认识。

这一节系统的讲一下COM的常用类型和函数,主要是COM的特殊性导致在使用特殊类型时会有很多坑,需要特殊指明,如有补充,请留言说明。


a.接口指针的使用

接口指针何时AddRef,何时Release,这是经常容易误使用的地方。简单来说遵循如下原则:

1.函数参数

(1).函数的输入参数不需要AddRef和Release

因为这时候接口指针的拥有者是调用此函数的例程,接口指针的生命周期控制由它来负责

(2).函数的输出参数需要AddRef

输出参数要脱离当前函数作用域使用,需要托管给COM管理,所以AddRef

(3).函数的输入-输出参数,需要先Release原来的接口指针,再对新的接口指针AddRef

对于外部的输入,用完即可释放;对于内部输出的新接口,需要AddRef

(4).函数的局部变量

局部变量只是对输入参数或输出参数的一个引用,在此作用域中保持有效,不需要AddRef和Release

2.共享访问

对于全局函数和类成员,因为涉及到多个函数或多线程共享访问的问题,所以在传给函数前需要AddRef,返回后调用Release,对于多线程还要注意AddRef和Release中的加锁访问

3.特殊优化

部分场合能够确认,一定不会被销毁,可以不用AddRef和Release,但是建议不要这么做。AddRef再Release是最保险的,且不会带来什么大的开销


b.字符串类型

COM中字符的类型是OLECHAR,对应字符串类型是LPOLESTR

其实,定义如下

typedef WCHAR OLECHAR;

即所有的字符串都是Unicode编码,类似L“str”,COM中使用OLESTR("str")来定义宽字符


我们知道C/C++中字符串都是‘\0’结尾,但是在VB、Java等语言中字符串不是如此,为了让这些语言正确处理COM字符串,COM中定义了新的字符串类型BSTR。BSTR在字符串的开头标记了当前字符串的长度,示意图如下:


可以看到BSTR前4个字节是当前字符串长度,最后填充的结束符\0不计入实际长度

对于如下语句

VOID TestBSTR(){BSTR bStr = ::SysAllocString(OLESTR("test bstr"));UINT nLen = ::SysStringLen(bStr);wprintf(L"BSTR is: %s\n", bStr);::SysFreeString(bStr);}

查看内存结构如下:


对应的变量如下


即对于字符串“test bstr”内存布局前4个字节标记长度为18,末尾还是\0


因为C++中BSTR还是\0结尾,清楚BSTR的内存结构后,在常用WCHAR*地方可以使用BSTR来正常工作,但是这也有许多坑

比如函数

LPWSTR GetWCHAR_Str(){LPWSTR p = NULL;p = new WCHAR[10];if (p){lstrcpy(p, L"test");}return p;}BSTR GetBSTR_Str(){BSTR p = NULL;p = ::SysAllocString(OLESTR("test"));return p;}VOID FreeStr(LPWSTR p){if (p){wprintf(L"free %s\n", p);delete [] p;}}
LPWSTR p1 = GetWCHAR_Str();FreeStr(p1);//okBSTR p2 = GetBSTR_Str();//FreeStr(p2);//error

这里分别分配BSTR和WCHAR*类字符串,都可以在正常使用WCHAR*的当使用,但是释放的时候就SysAllocString分配的使用delete释放有问题。

同理WCHAR*分配的,使用SysDestroyString肯定也有问题。


所以在WCHAR*和BSTR混用时一定要注意,坚持一个原则——使用什么方法分配的,就使用什么方法重分配或释放

常用的BSTR函数如下:

还有一点需要注意,BSTR中NULL是合法的字符串,只不过长度为0而已。因此在复制BSTR为WCHAR*时需要判断是否为NULL,参考如下实现

VOID CopyBSTR(LPWSTR pStr, BSTR bStr){lstrcpy(pStr, bStr==NULL ? L"" : bStr);}

c.可变类型参数或无类型参数

C/C++都是强类型参数,指定函数参数时一定是指定的类型,有没有办法一个参数支持传递多种类型的参数呢?

PHP、Python等脚本语言是无类型的,他们如何调用COM的呢?


这些都用到COM提供的VARIANT类型参数,这是一个联合体,支持各种类型的参数,定义如下:

struct tagVARIANT    {    union         {        struct __tagVARIANT            {            VARTYPE vt;            WORD wReserved1;            WORD wReserved2;            WORD wReserved3;            union                 {                LONGLONG llVal;                LONG lVal;                ...                BSTR bstrVal;                IUnknown *punkVal;                IDispatch *pdispVal;                SAFEARRAY *parray;                BYTE *pbVal;                ...        } __VARIANT_NAME_1;    } ;typedef VARIANT *LPVARIANT;
vt指明当前参数类型

union联合体包含实际数据内容


每种类型都对应了一种enum类型,如下

enum VARENUM    {VT_EMPTY= 0,VT_NULL= 1,VT_I2= 2,VT_I4= 3,...VT_BSTR= 8,VT_DISPATCH= 9,VT_ERROR= 10,VT_BOOL= 11,VT_VARIANT= 12,VT_UNKNOWN= 13,...    } ;

操作每种类型数据时可以直接找到对应union变量,最好是使用对应的操作宏,如下

#define V_UNION(X, Y)   ((X)->Y).../* Variant access macros */#define V_ISBYREF(X)     (V_VT(X)&VT_BYREF)#define V_ISARRAY(X)     (V_VT(X)&VT_ARRAY)#define V_ISVECTOR(X)    (V_VT(X)&VT_VECTOR)#define V_NONE(X)        V_I2(X)#define V_UI1(X)         V_UNION(X, bVal)#define V_UI1REF(X)      V_UNION(X, pbVal)#define V_I2(X)          V_UNION(X, iVal)#define V_I2REF(X)       V_UNION(X, piVal)#define V_I4(X)          V_UNION(X, lVal)#define V_I4REF(X)       V_UNION(X, plVal)#define V_I8(X)          V_UNION(X, llVal)#define V_I8REF(X)       V_UNION(X, pllVal)...#define V_DISPATCH(X)    V_UNION(X, pdispVal)#define V_DISPATCHREF(X) V_UNION(X, ppdispVal)


COM提供专门函数操作VARIANT

每个VARIANT类型参数需要初始化VariantInit,使用完需要调用对应函数释放资源VariantClear

不同的VARIANT类型支持类型转换VariantChangeType

使用实例如下:

    VOID TestVariant(VARIANT &v)    {        VARIANT v2;        VariantInit(&v2);        HRESULT hr = VariantChangeType(&v2, &v, 0, VT_BSTR);        if (SUCCEEDED(hr))        {            wprintf(L"BSTR is: %s\n", V_BSTR(&v2));        }        VariantClear(&v2);    }//Variant TypeVARIANT v;VariantInit(&v);V_VT(&v) = VT_I4;V_I4(&v) = 8;TestVariant(v);VariantClear(&v);


d.安全数组类型

C/C++中数组是常用类型,但是如何和其他类型语言传递数组,这是个问题。没有数组类型的语言怎么表示数组维数呢,数组下标不是从0开始的怎么表示?

为此COM设计了SAFEARRAY类型,如下为定义的维数数据结构


这里可以自定义索引的起始值

对应的SAFEARRAY结构如下


可以看到这个结构是一个描述数组信息的结构,真实数据保存在pvData中。

COM提供相关操作函数如下:

1.可以使用SafeArrayAllocDescriptor分配头部和SafeArrayAllocData分配实际数据,也可以使用SafeArrayCreate或SafeArrayCreateVector同时创建。

2.访问实际数据可以直接SafeArrayGetElement和SafeArrayPutElement按照设计的索引访问,也可以使用SafeArrayAccessData和SafeArrayUnaccessData直接访问Raw数据。

3.获取相关信息可以SafeArrayGetDim获得维数,SafeArrayGetLBound和SafeArrayGetUBound获取当前最小最大索引。


分别创建头部和数据区演示如下:

SAFEARRAY *psa;unsigned int ndim =  2;HRESULT hresult = SafeArrayAllocDescriptor( ndim, &psa );if( FAILED( hresult ) )   return ERR_OutOfMemory;(psa)->rgsabound[ 0 ].lLbound = 0;(psa)->rgsabound[ 0 ].cElements = 5;(psa)->rgsabound[ 1 ].lLbound = 1;(psa)->rgsabound[ 1 ].cElements = 4;hresult = SafeArrayAllocData( psa );if( FAILED( hresult ) ) {   SafeArrayDestroyDescriptor( psa )   return ERR_OutOfMemory;}

常用的访问实际数据方法如下:

VOID TestSafeArray(){SAFEARRAY* pArray = SafeArrayCreateVector(VT_I4, 3, 10); wprintf(L"Dims:%d\n", SafeArrayGetDim(pArray));long iLBound=0, iUBound=0;long *pRgn=NULL;if (SUCCEEDED(SafeArrayGetLBound(pArray, 1, &iLBound)) &&SUCCEEDED(SafeArrayGetUBound(pArray, 1, &iUBound)) &&SUCCEEDED(SafeArrayAccessData(pArray, (LPVOID*)&pRgn))){//data的索引必须从0开始for (int i=0; i<=iUBound-iLBound; i++){pRgn[i] = i*2;}//element的索引按照指定开始for (long i=iLBound; i<=iUBound; i++){long nVal = 0;SafeArrayGetElement(pArray, &i, (void*)&nVal);wprintf(L"Data:%d\n", nVal);}SafeArrayUnaccessData(pArray);pRgn = NULL;}if (pArray){SafeArrayDestroy(pArray);}}


注意这里的注释,Raw数据索引始终从0开始,Element数据索引从创建时指定的开始,比如这里创建时指定的是起始索引为3,数据个数为10,那么LBound=3,UBound=12


创建多维数组演示如下:

SAFEARRAYBOUND sb[2];sb[0].cElements = 3;sb[0].lLbound = 0;sb[1].cElements = 4;sb[1].lLbound = 0;SAFEARRAY* pSa = ::SafeArrayCreate(VT_I4, 2, sb);LONG pIndex[] = {1, 2};int nVal = 10;::SafeArrayPutElement(pSa, pIndex, (LPVOID)&nVal);

注意,和常规数组不一样的是,这里sb[0]指定最低维(最右),sb[1]指定最高维,下标{1,2}也是分别对应低维/高维,使用上和常规数组没什么差别,只是内存结构稍有不同,这里实际上是4行3列的结构。


关于更详细的SafeArray使用可以参考下这篇文章


f.HRESULT返回类型

几乎所有的COM函数返回值都是HRESULT,他是一个32整数值,如下图


不同的部分表示不同的含义,包括

1.严重程度位(severity code) 表示操作是成功还是失败

2.操作码(facility code) 表示对应于什么具体操作类型

3.信息码(information code) 给出精确的结果值


SDK中定义了常见的HRESULT值,比如HRESULT值STG_S_CONVERTED操作码为FACILITY_STORAGE(结构化存储操作相关),严重程度为SEVERITY_SUCCESS(成功执行)。对于一些通用操作,可以使用FACILITY_NULL,常用的通用操作返回值如下:

S_OK//成功执行S_FALSE//成功执行但有逻辑错误E_FAIL//执行失败E_NOTIMPL//方法没有实现E_UNEXPECTED//使用错误E_INVALIDARG//输入无效参数E_OUTOFMEMORY//分配内存不足

判断操作是成功还是失败,需要使用SUCCEED和FAILED宏,判断的是严重程度位的符号,宏定义如下:

#define SUCCEEDED(hr)(((HRESULT)(hr)) >= 0)#define FAILED(hr)(((HRESULT)(hr)) < 0)

COM提供MAKE_HRESULT实现自定义HRESULT,自定义一般使用FACILITY_ITF(与接口相关操作码),用户自定义INFOMATION CODE一般大于0x200,类似如下:

MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_ITF, 0x200+10)
关于FACILITY_ITF和0x200可以参考微软文档

演示代码下载链接

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

原创粉丝点击