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"