【轉載】COM 組件設計與應用(三)——數據類型

原文:http://vckbase.com/index.php/wv/1206.htmlphp

COM 組件設計與應用 系列文章:http://vckbase.com/index.php/piwz?&p=1html

1、前言數組

上回書介紹了GUID、CLSID、IID和接口的概念。本回的重點是介紹 COM 中的數據類型。咋還不介紹組件程序的設計步驟呀?咳......彆着急,彆着急!孔子曰:「飯要一口一口地吃」;老子語:「心急吃不了熱豆腐」,孫子云:「走一步看一步吧」 ...... 先掌握必要的知識,未來寫起程序來纔會駕輕就熟也:-)安全

走入正題以前,請你們緊緊記住一條原則:COM 組件是運行在分佈式環境中的。好比,你寫了一個組件程序(DLL或EXE),那麼使用者多是在本機的某個進程內加載組件(INPROC_SERVER);也多是從另外一個進程中調用組件的進程(LOCAL_SERVER);也多是在這臺計算機上調用地球那邊計算機上的組件(REMOTE_SERVER)。因此在理解和設計的時候,要時時刻刻想起這句話。快!拿出小本本,記下來!服務器

2、HRESULT 函數返回值網絡

每一個人在作程序設計的時候,都有他們各自的哲學思想。拿函數返回值來講,就有好多種形式。分佈式

函數 返回值 返回值信息
double sin(double)

浮點數值ide

計算正玄值
BOOL DeleteFile(LPCTSTR)

布爾值函數

文件刪除是否成功。如失敗,須要GetLastError()才能取得失敗緣由
void * malloc(size_t)

內存指針學習

內存申請,若是失敗,返回空指針 NULL
LONG RegDeleteKey(HKEY,LPCTSTR)

整數

刪除註冊表項。0表示成功,非0失敗,同時這個值就反映了失敗的緣由
UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)

整數

取得拖放文件信息。以不一樣的參數調用,則返回不一樣的含義:
一下子表示文件個數,一下子表示文件名長度,一下子表示字符長度
......  ......

...

......  ......

如此紛繁複雜的返回值,如此含義多變的返回值,使得你們在學習和使用的過程當中,增長了額外的困難。好了,COM 的設計規範終於對他們進行了統一。組件API及接口指針中,除了IUnknown::AddRef()和IUnknown::Release()兩個函數外,其它全部的函數,都以 HRESULT 做爲返回值。你們想象一個組件的接口函數好比叫Add(),完成2個整數的加法運算,在C語言中,咱們能夠以下定義:

1. long Add( long n1, long n2 )
2. {
3. return n1 + n2;
4. }

還記得剛纔咱們說的原則嗎?COM 組件是運行在分佈式環境中的。也就是說,這個函數可能運行在「地球另外一邊」的計算機上,既然運行在那麼遙遠的地方,就有可能出現服務器關機、網絡掉線、運行超時、對方不在服務區......等異常。因而,這個加法函數,除了須要返回運算結果之外,還應該返回一個值------函數是否被正常執行了。

1. HRESULT Add( long n1, long n2, long *pSum )
2. {
3. *pSum = n1 + n2;
4. return S_OK;
5. }

若是函數正常執行,則返回 S_OK,同時真正的函數運行結果則經過參數指針返回。若是遇到了異常狀況,則COM系統通過判斷,會返回相應的錯誤值。常見的返回值有:

HRESULT 含義
S_OK 0x00000000 成功
S_FALSE 0x00000001 函數成功執行完成,但返回時出現錯誤
E_INVALIDARG 0x80070057 參數有錯誤
E_OUTOFMEMORY 0x8007000E 內存申請錯誤
E_UNEXPECTED 0x8000FFFF 未知的異常
E_NOTIMPL 0x80004001 未實現功能
E_FAIL 0x80004005 沒有詳細說明的錯誤。通常須要取得 Rich Error 錯誤信息(注1)
E_POINTER 0x80004003 無效的指針
E_HANDLE 0x80070006 無效的句柄
E_ABORT 0x80004004 終止操做
E_ACCESSDENIED 0x80070005 訪問被拒絕
E_NOINTERFACE 0x80004002 不支持接口

圖1、HRESULT 的結構

HRESULT 實際上是一個雙字節的值,其最高位(bit)若是是0表示成功,1表示錯誤。具體參見 MSDN 之"Structure of COM Error Codes"說明。咱們在程序中若是須要判斷返回值,則可使用比較運算符號;switch開關語句;也可使用VC提供的宏:

1. HRESULT hr = 調用組件函數;
2. if( SUCCEEDED( hr ) ){...} // 若是成功
3. ......
4. if( FAILED( hr ) ){...} // 若是失敗
5. ......

3、UNICODE

計算機發明後,爲了在計算機中表示字符,人們制定了一種編碼,叫ASCII碼。ASCII碼由一個字節中的7位(bit)表示,範圍是0x00 - 0x7F 共128個字符。他們覺得這128個數字就足夠表示abcd....ABCD....1234 這些字符了。

咳......說英語的人就是「笨」!後來他們忽然發現,若是須要按照表格方式打印這些字符的時候,缺乏了「製表符」。因而又擴展了ASCII的定義,使用一個字節的所有8位(bit)來表示字符了,這就叫擴展ASCII碼。範圍是0x00 - 0xFF 共256個字符。

咳......說中文的人就是聰明!中國人利用連續2個擴展ASCII碼的擴展區域(0xA0之後)來表示一個漢字,該方法的標準叫GB-2312。後來,日文、韓文、阿拉伯文、臺灣繁體(BIG-5)......都使用相似的方法擴展了本地字符集的定義,如今統一稱爲 MBCS 字符集(多字節字符集)。這個方法是有缺陷的,由於各個國家地區定義的字符集有交集,所以使用GB-2312的軟件,就不能在BIG-5的環境下運行(顯示亂碼),反之亦然。

咳......說英語的人終於變「聰明」一些了。爲了把全世界人民全部的全部的文字符號都統一進行編碼,因而制定了UNICODE標準字符集。UNICODE 使用2個字節表示一個字符(unsigned shor int、WCHAR、_wchar_t、OLECHAR)。這下終於好啦,全世界任何一個地區的軟件,能夠不用修改地就能在另外一個地區運行了。雖然我用 IE 瀏覽日本網站,顯示出我不認識的日文文字,但至少不會是亂碼了。UNICODE 的範圍是 0x0000 - 0xFFFF 共6萬多個字符,其中光漢字就佔用了4萬多個。嘿嘿,中國人賺大發了:0)

在程序中使用各類字符集的方法:

01. const char * p = "Hello"// 使用 ASCII 字符集
02. const char * p = "你好"// 使用 MBCS 字符集,因爲 MBCS 徹底兼容 ASCII,多數狀況下,咱們並不嚴格區分他們
03. LPCSTR p = "Hello,你好"// 意義同上
04.  
05. const WCHAR * p = L"Hello,你好"// 使用 UNICODE 字符集
06. LPCOLESTR p = L"Hello,你好"// 意義同上
07.  
08. // 若是預約義了_UNICODE,則表示使用UNICODE字符集;若是定義了_MBCS,則表示使用 MBCS
09. const TCHAR * p = _T("Hello,你好");
10. LPCTSTR p = _T("Hello,你好"); // 意義同上

在上面的例子中,T是很是有意思的一個符號(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一種中間類型,既不明確表示使用 MBCS,也不明確表示使用 UNICODE。那到底使用哪一種字符集那?嘿嘿......編譯的時候決定吧。設置條件編譯的方式是:VC6中,"Project\Settings...\C/C++卡片 Preprocessor definitions" 中添加或修改 _MBCS、_UNICODE;VC.NET中,"項目\屬性\配置屬性\常規\字符集"而後用組合窗進行選擇。使用 T 類型,是很是好的習慣,嚴重推薦!

4、BSTR

COM 中除了使用一些簡單標準的數據類型外(注2),字符串類型須要特別重點地說明一下。還記得原則嗎?COM 組件是運行在分佈式環境中的。通俗地說,你不能直接把一個內存指針直接做爲參數傳遞給COM函數。你想一想,系統須要把這塊內存的內容傳遞到「地球另外一 邊」的計算機上,所以,我至少須要知道你這塊內存的尺寸吧?否則讓我如何傳遞呀?傳遞多少字節呀?!而字符串又是很是經常使用的一種類型,所以 COM 設計者引入了 BASIC 中字符串類型的表示方式---BSTR。BSTR 實際上是一個指針類型,它的內存結構是:(輸入程序片斷 BSTR p = ::SysAllocString(L"Hello,你好");斷點執行,而後觀察p的內存)

圖2、BSTR 內存結構

BSTR 是一個指向 UNICODE 字符串的指針,且 BSTR 向前的4個字節中,使用DWORD保存着這個字符串的字節長度( 沒有含字符串的結束符)。所以系統就可以正確處理並傳送這個字符串到「地球另外一 邊」了。特別須要注意的是,因爲BSTR的指針就是指向 UNICODE 串,所以 BSTR 和 LPOLESTR 能夠在必定程度上混用,但必定要注意:

有函數 fun(LPCOLESTR lp),則你調用 BSTR p=...; fun(p); 正確

有函數 fun(const BSTR bstr),則你調用 LPCOLESTR p=...; fun(p); 錯誤!!!

有關 BSTR 的處理函數:

API 函數 說明
SysAllocString() 申請一個 BSTR 指針,並初始化爲一個字符串
SysFreeString() 釋放 BSTR 內存
SysAllocStringLen() 申請一個指定字符長度的 BSTR 指針,並初始化爲一個字符串
SysAllocStringByteLen() 申請一個指定字節長度的 BSTR 指針,並初始化爲一個字符串
SysReAllocStringLen() 從新申請 BSTR 指針

CString 函數

說明

AllocSysString() 從 CString 獲得 BSTR
SetSysString() 從新申請 BSTR 指針,並複製到 CString 中

CComBSTR 函數

ATL 的 BSTR 包裝類。在 atlbase.h 中定義

Append()、AppendBSTR()、AppendBytes()、ArrayToBSTR()、BSTRToArray()、AssignBSTR()、Attach()、Detach()、Copy()、CopyTo()、Empty()、Length()、ByteLength()、ReadFromStream()、WriteToStream()、LoadString()、ToLower()、ToUpper()
運算符重載:!,!=,==,<,>,&,+=,+,=,BSTR
    太多了,但從函數名稱不能看出其基本功能。詳細資料,查看MSDN 吧。另外,左側函數,有不少是 ATL 7.0 提供的,VC6.0 下所帶的 ATL 3.0 不支持。
    因爲咱們未來主要用 ATL 開發組件程序,所以使用 ATL 的 CComBSTR 爲主。VC也提供了其它的包裝類 _bstr_t。

5、各類字符串類型之間的轉換

一、函數 WideCharToMultiByte(),轉換 UNICODE 到 MBCS。使用範例:

01. LPCOLESTR lpw = L"Hello,你好";
02. size_t wLen = wcslen( lpw ) + 1;  // 寬字符字符長度,+1表示包含字符串結束符
03.  
04. int aLen=WideCharToMultiByte(  // 第一次調用,計算所需 MBCS 字符串字節長度
05. CP_ACP,
06. 0,
07. lpw,  // 寬字符串指針
08. wLen, // 字符長度
09. NULL,
10. 0,  // 參數0表示計算轉換後的字符空間
11. NULL,
12. NULL);
13.  
14. LPSTR lpa = new char [aLen];
15.  
16. WideCharToMultiByte(
17. CP_ACP,
18. 0,
19. lpw,
20. wLen,
21. lpa,  // 轉換後的字符串指針
22. aLen, // 給出空間大小
23. NULL,
24. NULL);
25.  
26. // 此時,lpa 中保存着轉換後的 MBCS 字符串
27. ... ... ... ...
28. delete [] lpa;

二、函數 MultiByteToWideChar(),轉換 MBCS 到 UNICODE。使用範例:

01. LPCSTR lpa = "Hello,你好";
02. size_t aLen = strlen( lpa ) + 1;
03.  
04. int wLen = MultiByteToWideChar(
05. CP_ACP,
06. 0,
07. lpa,
08. aLen,
09. NULL,
10. 0);
11.  
12. LPOLESTR lpw = new WCHAR [wLen];
13. MultiByteToWideChar(
14. CP_ACP,
15. 0,
16. lpa,
17. aLen,
18. lpw,
19. wLen);
20. ... ... ... ...
21. delete [] lpw;

三、使用 ATL 提供的轉換宏。

A2BSTR OLE2A T2A W2A
A2COLE OLE2BSTR T2BSTR W2BSTR
A2CT OLE2CA T2CA W2CA
A2CW OLE2CT T2COLE W2COLE
A2OLE OLE2CW T2CW W2CT
A2T OLE2T T2OLE W2OLE
A2W OLE2W T2W W2T

上表中的宏函數,其實很是容易記憶:

2 好搞笑的縮寫,to 的發音和 2 同樣,因此借用來表示「轉換爲、轉換到」的含義。
A ANSI 字符串,也就是 MBCS。
W、OLE 寬字符串,也就是 UNICODE。
T 中間類型T。若是定義了 _UNICODE,則T表示W;若是定義了 _MBCS,則T表示A
C const 的縮寫

使用範例:

01. #include < atlconv.h >
02.  
03. void fun()
04. {
05. USES_CONVERSION;  // 只須要調用一次,就能夠在函數中進行屢次轉換
06.  
07. LPCTSTR lp = OLE2CT( L"Hello,你好") );
08. ... ... ... ...
09. // 不用顯式釋放 lp 的內存,由於
10. // 因爲 ATL 轉換宏使用棧做爲臨時空間,函數結束後會自動釋放棧空間。
11. }

使用 ATL 轉換宏,因爲不用釋放臨時空間,因此使用起來很是方便。可是考慮到棧空間的尺寸(VC 默認2M),使用時要注意幾點:

一、只適合於進行短字符串的轉換;

二、不要試圖在一個次數比較多的循環體內進行轉換;

三、不要試圖對字符型文件內容進行轉換,由於文件尺寸通常狀況下是比較大的;

四、對狀況 2 和 3,要使用 MultiByteToWideChar() 和 WideCharToMultiByte();

6、VARIANT

C++、BASIC、Java、Pascal、Script......計算機語言多種多樣,而它們各自又都有本身的數據類型,COM 產生目的,其中之一就是要跨語言(注3)。而 VARIANT 數據類型就具備跨語言的特性,同時它能夠表示(存儲)任意類型的數據。從C語言的角度來說,VARIANT 實際上是一個結構,結構中用一個域(vt)表示------該變量到底表示的是什麼類型數據,同時真正的數據則存貯在 union 空間中。結構的定義太長了(雖然長,但其實很簡單)你們去看 MSDN 的描述吧,這裏給出如何使用的簡單示例:

學生:我想用 VARIANT 表示一個4字節長的整數,如何作?

老師:VARIANT v; v.vt=VT_I4; v.lVal=100;

學生:我想用 VARIANT 表示布爾值「真」,如何作?

老師:VARIANT v; v.vt=VT_BOOL; v.boolVal=VARIANT_TRUE;

學生:這麼麻煩?我能不能 v.boolVal=true; 這樣寫?

老師:不能夠!由於 

類型 字節長度 假值 真值
bool 1(char) 0(false) 1(true)
BOOL 4(int) 0(FALSE) 1(TRUE)
VT_BOOL 2(short int) 0(VARIANT_FALSE) -1(VARIANT_TRUE)

因此若是你 v.boolVal=true 這樣賦值,那麼未來 if(VARIANT_TRUE==v.boolVal) 的時候會出問題(-1 != 1)。可是你注意觀察,任何布爾類型的「假」都是0,所以做爲一個好習慣,在作布爾判斷的時候,不要和「真值」相比較,而要與「假值」作比較。

學生:謝謝老師,你太牛了。我對老師的敬仰如滔滔江水,連綿不絕......

學生:我想用 VARIANT 保存字符串,如何作?

老師:VARIANT v; v.vt=VT_BSTR; v.bstrVal=SysAllocString(L"Hello,你好");

學生:哦......我明白了。但是這麼操做真夠麻煩的,有沒有簡單一些的方法?

老師:有呀,你可使用現成的包裝類 CComVariant、COleVariant、_variant_t。好比上面三個問題就能夠這樣書寫:CComVariant v1(100),v2(true),v3("Hello,你好"); 簡單了吧?!(注4)

學生:老師,我再問最後一個問題,我如何用 VARIANT 保存一個數組?

老師:這個問題很複雜,我如今不能告訴你,我如今告訴你怕你印象不深......(注5)

學生:~!@#$%^&*()......暈!

7、小結

以上所介紹的內容,是基本功,必須熟練掌握。先到這裏吧,休息一下子......更多精彩內容,敬請關注《COM 組件設計與應用(四)》

 

注1:在後續的 ISupportErrorInfo 接口中介紹。

注2:常見的數據類型,請參考 IDL 文件的說明。(彆着急,還沒寫那......嘿嘿)

注3:跨語言就是各類語言中都能使用COM組件。但啥時候能跨平臺呢?

注4:CComVariant/COlevariant/_variant_t 請參看 MSDN。

注5:關於安全數組 SafeArray 的使用,在後續的文章中討論。 

相關文章
相關標籤/搜索