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