COM库程序设计基础细节
数据类型
编辑字符串
编辑OLECHAR就是wchar_t(但对于Win16是char)。
BSTR是0结尾的,存储wchar_t字符的字符串(即wchar_t*),但在指针所指的第一个字符之前(即低地址)的4个字节保存了字符串的字节长度(不含结尾两个0字节,不含长度4个字节自身)。从而BSTR内容可包含null字节。BSTR字符串的分配和释放需要使用Windows API专门的字符串操纵函数。空指针被认为等效于包含了0个字节长度的BSTR。
在命名空间_com_util定义了在BSTR与char*之间转换的全局函数:
- ConvertStringToBSTR
- ConvertBSTRToString
_bstr_t类用于封装了BSTR。_bstr_t内部定义了一个私有类Data_t和一个私有的成员变量Data_t* m_Data。Data_t是使用了引用计数的智能指针,包含私有数据成员BSTR m_wstr。每个BSTR对象实例用一个Data_t对象实例管理。而_bstr_t类对象实例通过指针数据成员m_Data指向了一个Data_t对象实例,从而多个_bstr_t类对象实例可以指向同一个BSTR对象。Data_t的功能如下:
- 构造函数:Data_t可以传入char* 、wchar_t* 、BSTR类型来构造,也可以传入两个_bstr_t以其连接字符串的结构构造,这些都是分配内存空间并复制其值的方式来构造Data_t;也可以传入一个BSTR的对象,不复制而是直接管理该BSTR对象的方式来构造Data_t。
- 传出wchar_t*时,Data_t直接返回内部m_wstr保存的wchar_t*的指针值;传出char*时,Data_t准备一份字符串并在内部的m_str保存其地址,返回m_str。
- Data_t使用Copy成员函数复制一份BSTR并传出。
- Data_t使用Assign成员函数,先释放已有存储,然后重新申请内存、复制一份BSTR并管理。
- Data_t使用Attach成员函数,先释放已有存储,然后直接管理传入的BSTR对象。
- Data_t使用Length成员函数返回保存的字符数。
- Data_t使用Compare成员函数比较两个Data_t的字典序。
- Data_t使用成员函数_Free释放所管理的BSTR对象与/或char*字符串。
基于Data_t的内部功能,_bstr_t类功能如下:
- 拷贝构造:相当于引用计数加1
- 传入char*、wchar_t*、BSTR的构造函数:调用Data_t的相应构造函数,一般是申请内存复制内容,可以对BSTR对象直接管理
- 缺省构造
- 赋值_bstr_t对象的操作符:先释放内容,然后直接指向被赋值的_bstr_t对象的Data_t对象并引用计数加1
- 赋值char*、wchar_t*的操作符:先释放内容,然后内部构造新的Data_t对象
- 传入_bstr_t的+=操作符:释放老的内容,然后管理二个_bstr_t对象连接后的结果
- 传入_bstr_t对象的+操作符:实际效果同+=操作符
- char*与_bstr_t的友元运算符:实际效果类似+=成员运算符
- wchar_t*与_bstr_t的友元运算符:实际效果类似+=成员运算符
- 输出为wchar_t*、const wchar_t*、char*、const char*:返回内部Data_t保存的内存地址
- !运算符:保存内容是否为空
- ==、!=、<、<=、>、>=比较运算符:字典序
- copy成员函数:复制一份新的BSTR,或者根据输入参数而直接返回保存的BSTR地址
- length成员函数:返回保存内容的字符数
- Assign成员函数:先释放内容,然后申请内容复制传入的BSTR
- GetBSTR成员函数:返回保存的BSTR地址
- GetAddress成员函数:释放内容,然后返回内部Data_t的m_wstr数据成员的内存地址
- Attach成员函数:释放内容,然后构造新的Data_t对象管理传入的BSTR对象
- Detach成员函数:解除对内部管理的BSTR对象的绑定
- _AddRef成员函数:引用计数加1
- _Free成员函数:引用计数减1,m_data数据成员置空
- _Compare成员函数:字典序
DECIMAL结构
编辑DECIMAL结构在.Net、Visual Basic、C#、SQL Server都有广泛应用。但C/C++编程时缺乏相关的API。存储长度128比特。其结构为:
typedef struct tagDEC {
USHORT wReserved;
union {
struct {
BYTE scale; //值0至28,表示小数点位置。例如12.345表示为12345且scale为3
BYTE sign; //正数的sign表示为0,负数的sign表示为DECIMAL_NEG
};
USHORT signscale;
};
ULONG Hi32;
union {
struct {
ULONG Lo32;
ULONG Mid32;
};
ULONGLONG Lo64;
};
} DECIMAL;
DECIMAL对象实际存储 96比特(即12个字节)无符号整型,并除以一个10的scale幂数。这个scale变比因子决定了小数点右面的数字位数,其范围从0到28。变比因子为 0(没有小数位)的情形下,最大的可表示值为 +/- 79,228,162,514,264,337,593,543,950,335;在有28个小数位的情况下,最大可表示值为 +/- 7.9228162514264337593543950335;最小的非零值为 +/-0.0000000000000000000000000001;
类型 | 表示范围 | 精度(十进制数字) |
---|---|---|
float | ±1.5×10-45到±3.4×1038 | 7位 |
double | ±5.0×10-324到±1.7×10308 | 15到16位 |
DECIMAL | ±1.0×10-28到±7.9×1028 | 28到29位 |
由于Windows API没有DECIMAL的相关函数,因此保存DECIMAL值需要自己写程序。或者使用_variant_t类的成员函数ChangeType读写DECIMAL的值。DECIMAL的四则运算可使用DECIMAL算术API函数,如VarDecAdd、VarDecMul等;或者使用Variant算术API函数,如VarAdd、VarMul等。
DATE类型
编辑DATE类型被定义为double的同类型。两个Windows API函数:VariantTimeToSystemTime与SystemTimeToVariantTime在DATE类型与SYSTEMTIME之间转换,但是SystemTimeToVariantTime四舍五入了毫秒。
DATE类型表示范围从公元100年1月1日至9999年12月31日,包含两端。值2.0表示1900年1月1日;值3.0表示1900年1月2日;以此类推。增加1表示日期增加了1天。小数部分表示1天内的时刻。因此,值2.5表示1900年1月1日中午12时;值3.25表示1900年1月2日6:00;等等。因此,0.5表示12个小时,即12*60*60秒;因此1秒= 0.5/(12*60*60) = .0000115740740740。
CY类型
编辑CY类型表示通货。实际上与__int64同类型。 其值除以10 000为实际表示的货币数量。其表示精度在整数部分为15个十进制数字,小数部分为4个十进制数字。有效范围从-922,337,203,685,477.5808到922,337,203,685,477.5807。在ACCESS等数据库,字面常量使用后缀( @ ).
CY类型的运算可使用Windows API函数VarCyAdd、VarCyMul等。
VARIANT的包装类_variant_t
编辑VARIANT是一个POD数据结构。简化地说包含两个域。vt域是个枚举值,描述了第二个域的数据类型。第二个域定义为一个联合结构,用来存储多种数据类型的值。所以,第二个域的名称随着vt域中输入值的不同而改变。数据的所有权必须遵从下述规则:
- 使用前必须调用函数VariantInit初始化
- 类型VT_UI1, VT_I2, VT_I4, VT_R4, VT_R8, VT_BOOL, VT_ERROR, VT_CY, VT_DECIMAL,VT_DATE存储在VARIANT结构中。
- 使用VT_BYREF 位或 其它类型,variant所指向的内存,由函数调用者拥有并释放。
- VT_BSTR,必须使用SysAllocString申请,使用SysFreeString释放。
- VT_ARRAY 位或 其它类型,必须用SafeArrayCreate创建,用SafeArrayDestroy释放。
- VT_DISPATCH 与 VT_UNKNOWN,指向的是COM对象,必须用Release函数来释放。
VARIANT 与 VARIANTARG 在语法上相同,但语义不同。
- VARIANT包含的值不允许是引用情形,如 VT_BYREF | VT_I4,即不允许用VT_BYREF;但 VARIANTARG 允许其值为引用情形;
- VARIANTARG 最多只能为一层引用,即其值是VT_VARIANT | VT_BYREF,而这个被引用的就不能还是VT_VARIANT | VT_BYREF类型。
- VARIANTARG 可以作为 DISPPARAMS(dispatch 函数的实参结构)。因为COM库保障了实参是引用类型时的正确处置。
- VARIANT可以传值;VARIANTARG不能用作传值,即不允许拷贝raw pointer。
- 函数VariantCopyInd输入一个VARIANTARG,转化为VARIANT, 去除了所有VT_BYREF
_variant_t是在VARIANT之上派生的C++类。包括了众多的成员函数。
- 构造函数
- 缺省构造函数:vt域置为VT_EMPTY
- 拷贝构造函数以及传入const VARIANT& 、const VARIANT*:对VT_BSTR或VT_ARRAY复制其内容;对VT_DISPATCH或VT_UNKNOWN,自动调用AddRef;对传引用的COM对象,即VT_DISPATCH | VT_BYREF或VT_UNKNOWN | VT_BYREF,使用者的责任决定是否调用IUnknown::AddRef。
- 传入参数为(VARIANT&, bool fCopy)的构造函数:如果!fCopy,那么采取移动构造语义;否则采取复制构造语义
- 传入参数为(short sSrc, VARTYPE vtSrc)的构造函数:使用VT_I2或VT_BOOL的值初始化
- 传入参数为(long lSrc, VARTYPE vtSrc)的构造函数:使用VT_I4、VT_ERROR、VT_BOOL的值初始化
- 传入参数为double dblSrc, VARTYPE vtSrc的构造函数:使用VT_R8或VT_DATE的值初始化
- 传入参数为float、const CY&、const _bstr_t&、const wchar_t *、const char*、(IDispatch* pSrc, bool fAddRef = true)、bool、(IUnknown* pSrc, bool fAddRef = true)、const DECIMAL&、BYTE、char、unsigned short、unsigned long、int、unsigned int、__int64、unsigned __int64等类型的构造函数
- 析构函数
- 类型转换运算符:抽取值
- 赋值运算符:清理掉原值,保存新值。注意对于字符串,采取复制语义
- 相等运算符、不等运算符:与VARIANT比较
- Clear() , 调用::VariantClear(),释放内存,vt域设为VT_EMPTY
- Attach(VARIANT& varSrc) :移动语义
- Detach() :移动语义
- VARIANT& GetVARIANT() :返回基类
- VARIANT* GetAddress() :清空内容,返回基类对象地址
- void ChangeType(VARTYPE vartype, const _variant_t* pSrc = NULL) :调用::VariantChangeType
- void SetString(const char* pSrc)
_com_ptr_t类模板
编辑_com_ptr_t类模板,封装了COM interface pointer,并且是一个智能指针,实现了引用计数。它的模板参数是interface类型名与对应的iid。数据成员是m_pInterface。比较特殊的成员函数是CreateInstance()与GetActiveObject(),以及QueryInterface()
SAFEARRAY结构
编辑SafeArray就是将通常的数组增加一个描述符,说明其维数、长度、边界、元素类型等信息。SafeArray也并不单独使用,而是将其再包装到VARIANT类型的变量中,然后才作为参数传送出去。在VARIANT的vt成员的值如果包含VT_ARRAY|...,那么它所封装的就是一个SafeArray,它的parray成员即是指向SafeArray的指针。SafeArray中元素的类型可以是VARIANT能封装的任何类型,包括VARIANT类型本身。 接收方不可以修改SAFEARRAY的数据,只能读或者销毁。
SAFEARRAY结构,包括下述成员:
- USHORT cDims; //数组的维数
- USHORT fFeatures; //flags
- ULONG cbElements;//每一个元素的size,在SafeArrayAllocData()之前必须给定该域的值
- ULONG cLocks; //计数器,用来跟踪该数组被锁定的次数
- PVOID pvData; //指向真实数据
- SAFEARRAYBOUND rgsabound[ 1 ]; //用于描述每一维,rgsabound[0]用于规定最左维,rgsabound[cDims-1]规定最右维
其中结构SAFEARRAYBOUND包括下述成员,数组的每一维对应一个结构SAFEARRAYBOUND对象
- ULONG cElements; //该维的元素数目
- LONG lLbound; //该维的起始索引值
主要API函数有:
- SafeArrayCreate()建立多维普通数组。
- SafeArrayCreateVector()用于建立一维普通数组。
- SafeArrayAllocDescriptor()
- SafeArrayAllocData()
- SafeArrayDestroy(psa) 销毁Array应该使用
创建Array例子1:
SAFEARRAYBOUND rgsabound[2];
rgsabound[0].cElements = 3; rgsabound[0].lLbound = 0;
rgsabound[1].cElements = 4; rgsabound[1].lLbound = 1;
SAFEARRAY* psa = ::SafeArrayCreate(VT_R8, 2, rgsabound);
创建Array例子2:
//创建SAFEARRAY数组,每个元素为long型,该数组是一维数组
long nData[10]={1,2,3,4,5,6,7,8,9,10};
SAFEARRAY* pArray=NULL;
HRESULT hr=SafeArrayAllocDescriptor(1,&pArray);//创建SAFEARRAY结构的对象
pArray->cbElements=sizeof(nData[0]);
pArray->rgsabound[0].cElements=10;
pArray->rgsabound[0].lLbound=0;
pArray->pvData=nData;
pArray->fFeatures=FADF_AUTO|FADF_FIXEDSIZE;//FADF_AUTO指定在栈上分配数据,并且大小不可以改变(固定为10)
读取Array中数据例子1:
long rgIndices[] = { 2, 1 };
double lElem;
::SafeArrayGetElement(psa, rgIndices, (void*)&lElem);
注意上面例子取得[1][2]的数据.
读取Array中数据例子2:
//访问SAFEARRAY数组
long* pValue=NULL;
SafeArrayAccessData(pArray,(void**)&pValue);//取得数据存到指针pValue
long Low(0),High(0);
hr=SafeArrayGetLBound(pArray,1,&Low);//维数索引从1开始
hr=SafeArrayGetUBound(pArray,1,&High);//维数索引从1开始
SafeArrayUnaccessData(pArray);//取消数据访问
SafeArrayDestroy(pArray);//销毁
注意上面例子取得[1][2]的数据.
多线程访问一个安全数组时,要由程序员自己做并发控制。 若要在一个变量中存储安全数组,只需要简单地将变量类型成员(VARIANT.vt)与VT_ARRAY 标记做或(OR)运算即可,如下所示:
VARIANT v1;
VariantInit(&v1);
v1.vt = VT_I4 | VT_ARRAY; // Array of 4 byte integers
v1.parray = pSa;
Visual Basic的过程或函数的参数如果是数组,则就是一个SafeArray.
接口
编辑类型库
编辑类型库(type library)是COM组件定义文件,包含OLE应用程序暴露出的类型与对象的信息。一个类型库可以是一个单独的二进制文件(.tlb),编译后的应用程序文件(.exe),嵌入到ActiveX控件(.ocx)中,或包含在动态链接库(.dll)文件中。有一个或多个类型库的DLL文件也常被编译为对象(.olb)库。[1]
可用Visual Studio的Object Browser窗口(从View下拉菜单中打开)查看类型库。在窗口顶部点击“”按钮,在打开的对话框中可以选择.NET、COM、Projects、Browse、Recent等方法指定类型库。其中Browse方法是给出类型库的文件路径。
type description
编辑这个显然是为了不同语言都能查询、使用COM对象。一个类型库type library包括多个类型,每个类型包含多个成员变量(分别用put、get等属性方法来访问)与成员函数。分别用ITypeLib、ITypeInfo、VARDESC、FUNCDESC表示。遍历“多个”时一般用基于0的计数。处理过程如下:
OleInitialize();
LoadTypeLib(wstrLibName, &typeLib_);//可以是exe/dll/tlb/olb。其中.idl在VisualStudio中编译为二进制的类型描述文件.tlb
nofTypeInfos_ = typeLib_->GetTypeInfoCount();//当前类型库有多少个type
typeLib_->GetDocumentation(略);//类型库的名字
typeLib_-> GetTypeInfo(curTypeInfo_, &curITypeInfo_);//用curTypeInfo_从零开始遍历每一个类型
//也可以获得the type information通过GetTypeInfoOfGuid()
curITypeInfo_ -> GetTypeAttr(&curTypeAttr_);//对当前类型查其属性
curITypeInfo_ -> GetDocumentation(略);//类型的名字
//根据curTypeAttr->typekind判断是Enum、Record、Module、Interface、Dispatch、CoClass、Alias、Union、Max之一。
//根据curTypeAttr->cFuncs或者cVars得知该类型中的成员变量数目、成员函数数目
curITypeInfo_-> GetFuncDesc(curFunc_, &curFuncDesc_);//用curFunc_从零开始遍历类型内的每个函数,获得其描述信息,如参数个数、可选参数个数、函数类型(虚函数或者dispatch函数等)、函数返回类型、函数执行的类别(func、put、get、putref等)、参数的类型(域lprgelemdescParam,包括数据类型、in/out/lcid区域设置/ret/optional/缺省值/cust)等
curITypeInfo_->GetNames(memid, rgBstrNames,cMaxNames,*pcNames );//根据member id,获得类型内第memid的成员的名字,包括函数的参数的名字。
curITypeInfo->GetImpleTypeFlags(curFunc_, &curImplTypeFlags_);//得到第curFunc_个函数的实现flags:如被调用而非实现、限于内部使用,等等
//下面讨论类型内部的成员变量:
curITypeInfo_->GetVarDesc(curVar_, &curVarDesc_);//从零计数的第curVar_个变量的描述,包括变量类型、VARKIND(instance/static/const/dispatch四种)、类内偏移值(如为instance)、常量值(如为const)
curITypeInfo_->GetNames(curVarDesc_->memid, &varName, 1, &dummy);//变量名字
常用的COM API
编辑- CLSIDFromString 输入为宽字符串表示的CLSID,输出为GUID结构的clsid
- CLSIDFromProgID 输入为ProgID字符串,输出为
- ProgIDFromCLSID
- StringFromCLSID
- StringFromGUID2
- StringFromIID
- CoCreateGuid
- CoCreateInstance
- CoFileTimeNow
- CoGetClassObject
常用的自动化API
编辑Disp API
编辑- DispGetIDsOfNames
- DispGetParam
- DispInvoke
TypeLib API
编辑- CreateTypeLib2
- LoadTypeLib
- LoadRegTypeLib
- RegisterTypeLib
- UnRegisterTypeLib
BSTR API
编辑- SysAddRefString
- SysAllocString
- SysAllocStringByteLen
- SysAllocStringLen
- SysFreeString
- SysReleaseString
- SysReAllocString
- SysReAllocStringLen
- SysStringByteLen
- SysStringLen
- VarBstrCat
- VarBstrCmp
SAFE ARRAY API
编辑- BstrFromVector
- SafeArrayAccessData
- SafeArrayAllocData
- SafeArrayAllocDescriptor
- SafeArrayCopy
- SafeArrayCopyData
- SafeArrayCreate
- SafeArrayCreateVector
- SafeArrayDestroy
- SafeArrayDestroyData
- SafeArrayDestroyDescriptor
- SafeArrayGetDim
- SafeArrayGetElement
- SafeArrayGetElemsize
- SafeArrayGetLBound
- SafeArrayGetUBound
- SafeArrayLock
- SafeArrayPtrOfIndex
- SafeArrayPutElement
- SafeArrayRedim
- SafeArrayUnaccessData
- SafeArrayUnlock
- VectorFromBstr
Error Info API
编辑- CreateErrorInfo
- GetErrorInfo
- SetErrorInfo
VARIANT API
编辑- SystemTimeToVariantTime
- VarBoolFromCy
- VarBoolFromDate
- VarBoolFromDec
- VarBoolFromDisp
- VarBoolFromI2
- VarBoolFromI4
- VarBoolFromR4
- VarBoolFromR8
- VarBoolFromStr
- VarBoolFromUI1
- VarBstrFromBool
- VarBstrFromCy
- VarBstrFromDate
- VarBstrFromDec
- VarBstrFromDisp
- VarBstrFromI2
- VarBstrFromI4
- VarBstrFromR4
- VarBstrFromR8
- VarBstrFromUI1
- VarCyFromBool
- VarCyFromDate
- VarCyFromDec
- VarCyFromDisp
- VarCyFromI2
- VarCyFromI4
- VarCyFromR4
- VarCyFromR8
- VarCyFromStr
- VarCyFromUI1
- VarDateFromBool
- VarDateFromCy
- VarDateFromDec
- VarDateFromDisp
- VarDateFromI2
- VarDateFromI4
- VarDateFromR4
- VarDateFromR8
- VarDateFromStr
- VarDateFromUdate
- VarDateFromUI1
- VarDecFromBool
- VarDecFromCy
- VarDecFromDate
- VarDecFromDisp
- VarDecFromI2
- VarDecFromI4
- VarDecFromR4
- VarDecFromR8
- VarDecFromStr
- VarDecFromUI1
- VarI2FromBool
- VarI2FromCy
- VarI2FromDate
- VarI2FromDec
- VarI2FromDisp
- VarI2FromI4
- VarI2FromR4
- VarI2FromR8
- VarI2FromStr
- VarI2FromUI1
- VarI4FromBool
- VarI4FromCy
- VarI4FromDate
- VarI4FromDec
- VarI4FromDisp
- VarI4FromI2
- VarI4FromR4
- VarI4FromR8
- VarI4FromStr
- VarI4FromUI1
- VariantChangeType
- VariantChangeTypeEx
- VariantClear
- VariantCopy
- VariantCopyInd
- VariantInit
- VariantTimeToSystemTime
- VarNumFromParseNum
- VarParseNumFromStr
- VarR4FromBool
- VarR4FromCy
- VarR4FromDate
- VarR4FromDec
- VarR4FromDisp
- VarR4FromI2
- VarR4FromI4
- VarR4FromR8
- VarR4FromStr
- VarR4FromUI1
- VarR8FromBool
- VarR8FromCy
- VarR8FromDate
- VarR8FromDec
- VarR8FromDisp
- VarR8FromI2
- VarR8FromI4
- VarR8FromR4
- VarR8FromStr
- VarR8FromUI1
- VarUdateFromDate
- VarUI1FromBool
- VarUI1FromCy
- VarUI1FromDate
- VarUI1FromDec
- VarUI1FromDisp
- VarUI1FromI2
- VarUI1FromI4
- VarUI1FromR4
- VarUI1FromR8
- VarUI1FromStr
VARTYPE Math API
编辑- VarAdd
- VarAnd
- VarCat
- VarDiv
- VarEqv
- VarIdiv
- VarImp
- VarMod
- VarMul
- VarOr
- VarPow
- VarSub
- VarXor
- VarAbs
- VarFix
- VarInt
- VarNeg
- VarNot
- VarRound
- VarCmp
Decimal math API
编辑- VarDecAdd
- VarDecDiv
- VarDecMul
- VarDecSub
- VarDecAbs
- VarDecFix
- VarDecInt
- VarDecNeg
- VarDecRound
- VarDecCmp
- VarDecCmpR8
Currency math API
编辑- VarCyAdd
- VarCyMul
- VarCyMulI4
- VarCyMulI8
- VarCySub
- VarCyAbs
- VarCyFix
- VarCyInt
- VarCyNeg
- VarCyRound
- VarCyCmp
- VarCyCmpR8
Format API
编辑- VarFormat
- VarFormatDateTime
- VarFormatNumber
- VarFormatPercent
- VarFormatCurrency
- VarWeekdayName
- VarMonthName
- VarFormatFromTokens
- VarTokenizeFormatString