原文: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 的使用,在後續的文章中討論。