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。
  • 函数Variant­Copy­Ind输入一个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 StudioObject 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

参考文献编辑

  1. in MSDN, "How to: View Type Library Information"