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
- 9.COM中特殊类型和相关函数
- 属性类型和相关函数
- MySQL 时间日期类型和相关函数
- 初始化COM库的函数和相关链接文件
- 特殊函数和成员
- 特殊类型BOOL和nil
- 4.9Python与中类型相关的内置函数
- matlab 特殊函数/特殊变量和常量
- ODE手册(6)关节类型和相关函数
- PHP 处理特殊字符相关函数
- JavaScript中Number类型,Number,parseInt转换函数特殊情况总结
- leetcode中常用函数和类型
- PHP中变量类型之特殊类型:资源类型、空类型
- 特殊函数的前缀和
- [转]C#和VB.NET中类型相关资料整理
- java中特殊的String类型
- java中特殊的String类型
- java中特殊的String类型
- 使用ArrayList创建帕斯卡三角
- 安卓自定义View雷达图(蜘蛛图)教程
- Kubernetes Node Controller源码分析之执行篇
- Java基础知识1
- MySQL优化
- 9.COM中特殊类型和相关函数
- Servlet之属性和监听者
- RHEL6配置yum源
- 怎样成为技术达人
- redis整合spring(redisTemplate工具类)
- JAVA企业面试题精选 Java SE 61-70
- 数据库的ACID
- 第三方水波纹效果
- DC