資料型別轉換,寬位元組和單位元組互轉
以前一直使用ATL的轉化巨集,怕不搞程式碼忘記了,特留個標籤。
一、前言
上回書介紹了GUID、CLSID、IID和介面的概念。本回的重點是介紹 COM 中的資料型別。咋還不介紹元件程式的設計步驟呀?咳......彆著急,彆著急!孔子曰:“飯要一口一口地吃”;老子語:“心急吃不了熱豆腐”,孫子云:“走一步看一步吧” ...... 先掌握必要的知識,將來寫起程式來才會得心應手也:-)
走入正題之前,請大家牢牢記住一條原則:COM 元件是執行在分散式環境中的。比如,你寫了一個元件程式(DLL或EXE),那麼使用者可能是在本機的某個程序內載入元件(INPROC_SERVER);也可能是從另一個程序中呼叫元件的程序(LOCAL_SERVER);也可能是在這臺計算機上呼叫地球那邊計算機上的元件(REMOTE_SERVER)。所以在理解和設計的時候,要時時刻刻想起這句話。快!拿出小本本,記下來!
二、HRESULT 函式返回值
每個人在做程式設計的時候,都有他們各自的哲學思想。拿函式返回值來說,就有好多種形式。
函式 | 返回值 | 返回值資訊 |
double sin(double) |
浮點數值 |
計算正玄值 |
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語言中,我們可以如下定義:
long Add( long n1, long n2 ) { return n1 + n2; }還記得剛才我們說的原則嗎?COM 元件是執行在分散式環境中的。也就是說,這個函式可能執行在“地球另一邊”的計算機上,既然執行在那麼遙遠的地方,就有可能出現伺服器關機、網路掉線、執行超時、對方不在服務區......等異常。於是,這個加法函式,除了需要返回運算結果以外,還應該返回一個值------函式是否被正常執行了。
HRESULT Add( long n1, long n2, long *pSum ) { *pSum = n1 + n2; return S_OK; }如果函式正常執行,則返回 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 | 不支援介面 |
圖一、HRESULT 的結構
HRESULT 其實是一個雙位元組的值,其最高位(bit)如果是0表示成功,1表示錯誤。具體參見 MSDN 之"Structure of COM Error Codes"說明。我們在程式中如果需要判斷返回值,則可以使用比較運算子號;switch開關語句;也可以使用VC提供的巨集:
HRESULT hr = 呼叫元件函式; if( SUCCEEDED( hr ) ){...} // 如果成功 ...... if( FAILED( hr ) ){...} // 如果失敗 ......
三、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)
在程式中使用各種字符集的方法:
const char * p = "Hello"; // 使用 ASCII 字符集 const char * p = "你好"; // 使用 MBCS 字符集,由於 MBCS 完全相容 ASCII,多數情況下,我們並不嚴格區分他們 LPCSTR p = "Hello,你好"; // 意義同上 const WCHAR * p = L"Hello,你好"; // 使用 UNICODE 字符集 LPCOLESTR p = L"Hello,你好"; // 意義同上 // 如果預定義了_UNICODE,則表示使用UNICODE字符集;如果定義了_MBCS,則表示使用 MBCS const TCHAR * p = _T("Hello,你好"); LPCTSTR p = _T("Hello,你好"); // 意義同上在上面的例子中,T是非常有意思的一個符號(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一種中間型別,既不明確表示使用 MBCS,也不明確表示使用 UNICODE。那到底使用哪種字符集那?嘿嘿......編譯的時候決定吧。設定條件編譯的方式是:VC6中,"Project/Settings.../C/C++卡片 Preprocessor definitions" 中新增或修改 _MBCS、_UNICODE;VC.NET中,"專案/屬性/配置屬性/常規/字符集"然後用組合窗進行選擇。使用 T 型別,是非常好的習慣,嚴重推薦!
四、BSTR
COM 中除了使用一些簡單標準的資料型別外(注2),字串型別需要特別重點地說明一下。還記得原則嗎?COM 元件是執行在分散式環境中的。通俗地說,你不能直接把一個記憶體指標直接作為引數傳遞給COM函式。你想想,系統需要把這塊記憶體的內容傳遞到“地球另一 邊”的計算機上,因此,我至少需要知道你這塊記憶體的尺寸吧?不然讓我如何傳遞呀?傳遞多少位元組呀?!而字串又是非常常用的一種型別,因此 COM 設計者引入了 BASIC 中字串型別的表示方式---BSTR。BSTR 其實是一個指標型別,它的記憶體結構是:(輸入程式片段 BSTR p = ::SysAllocString(L"Hello,你好");斷點執行,然後觀察p的記憶體)
圖二、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。 |
五、各種字串型別之間的轉換
1、函式 WideCharToMultiByte(),轉換 UNICODE 到 MBCS。使用範例:
LPCOLESTR lpw = L"Hello,你好"; size_t wLen = wcslen( lpw ) + 1; // 寬字元字元長度,+1表示包含字串結束符 int aLen=WideCharToMultiByte( // 第一次呼叫,計算所需 MBCS 字串位元組長度 CP_ACP, 0, lpw, // 寬字串指標 wLen, // 字元長度 NULL, 0, // 引數0表示計算轉換後的字元空間 NULL, NULL); LPSTR lpa = new char [aLen]; WideCharToMultiByte( CP_ACP, 0, lpw, wLen, lpa, // 轉換後的字串指標 aLen, // 給出空間大小 NULL, NULL); // 此時,lpa 中儲存著轉換後的 MBCS 字串 ... ... ... ... delete [] lpa;
2、函式 MultiByteToWideChar(),轉換 MBCS 到 UNICODE。使用範例:
LPCSTR lpa = "Hello,你好"; size_t aLen = strlen( lpa ) + 1; int wLen = MultiByteToWideChar( CP_ACP, 0, lpa, aLen, NULL, 0); LPOLESTR lpw = new WCHAR [wLen]; MultiByteToWideChar( CP_ACP, 0, lpa, aLen, lpw, wLen); ... ... ... ... delete [] lpw;
3、使用 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 的縮寫 |
使用範例:
#include <atlconv.h> void fun() { USES_CONVERSION; // 只需要呼叫一次,就可以在函式中進行多次轉換 LPCTSTR lp = OLE2CT( L"Hello,你好") ); ... ... ... ... // 不用顯式釋放 lp 的記憶體,因為 // 由於 ATL 轉換巨集使用棧作為臨時空間,函式結束後會自動釋放棧空間。 }使用 ATL 轉換巨集,由於不用釋放臨時空間,所以使用起來非常方便。但是考慮到棧空間的尺寸(VC 預設2M),使用時要注意幾點:
1、只適合於進行短字串的轉換;
2、不要試圖在一個次數比較多的迴圈體內進行轉換;
3、不要試圖對字元型檔案內容進行轉換,因為檔案尺寸一般情況下是比較大的;
4、對情況 2 和 3,要使用 MultiByteToWideChar() 和 WideCharToMultiByte();
六、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)
學生:[email protected]#$%^&*()......暈!
七、小結
以上所介紹的內容,是基本功,必須熟練掌握。先到這裡吧,休息一會兒......更多精彩內容,敬請關注《COM 元件設計與應用(四)》
注1:在後續的 ISupportErrorInfo 介面中介紹。
注2:常見的資料型別,請參考 IDL 檔案的說明。(彆著急,還沒寫那......嘿嘿)
注3:跨語言就是各種語言中都能使用COM元件。但啥時候能跨平臺呢?
注4:CComVariant/COlevariant/_variant_t 請參看 MSDN。
注5:關於安全陣列 SafeArray 的使用,在後續的文章中討論。
相關推薦
資料型別轉換,寬位元組和單位元組互轉
以前一直使用ATL的轉化巨集,怕不搞程式碼忘記了,特留個標籤。 一、前言 上回書介紹了GUID、CLSID、IID和介面的概念。本回的重點是介紹 COM 中的資料型別。咋還不介紹元件程式的設計步驟呀?咳......彆著急,彆著急!孔子曰:“飯要一口一口地吃”;老子語:
C#之資料型別轉換,迴圈和三元表示式使用方法
轉換資料型別 Convert.To…… 想把資料轉換成什麼型別就寫些什麼樣的,在convert.To直接加 //這一行程式碼要用int型別的變數來接收,那麼可以說,這個方法的返回值是int型別 Int numbers=convert.ToInt32(“4”);
js---資料型別轉換,typeof
js中有六種資料型別,包括五種基本資料型別(Number,String,Boolean,Undefined,Null),和一種複雜資料型別(Object)。 1.typeof------判斷後返回的都是字串型別("string" "number" "undefined" "
java基礎複習第二天——基本資料型別,引用資料型別,資料型別轉換,運算子
目錄 1.基本資料型別(4類八種) 2.引用資料型別 3.基本型別和引用型別的區別 4.資料型別轉換 5.運算子 ----------------------------------------------------------------------我是分割線--
java中的BigDecimal和String的相互轉換,int和String的型別轉換,Integer類和String相互轉換
一: /*由數字字串構造BigDecimal的方法 *設定BigDecimal的小數位數的方法 */ 注:BigDecimal在資料庫中存的是number型別。 import java.math.BigDecimal; //數字字串 String StrBd="1048576.1024"; /
Java資料型別轉換知識與字串和整形的互相轉換
一、資料型別 八大基本型別(想具體研究資料型別的可以參考這個網站:https://blog.csdn.net/u014266877/article/details/54374867?utm_source=copy): byte:8位,最大儲存資料量是255,存放的資料範圍是-128~127之
python教程6--自定義函式,資料型別轉換,解方程
本文主要講解點如下: 簡單函式 資料型別轉換 空函式 自定義絕對值函式 自定義函式 檢查引數型別 函式返回多個值 求解ax2 + bx + c = 0 的根 具體程式碼如下: '函式相關' __author__ = 'momo' impo
CString型別轉換,字串操作和UNICODE、ANSI字符集
一.CString與LPCWSTR 兩者的不同:LPCWSTR 是Unicode字串指標,初始化時串有多大,申請空間就有多大,以後存貯若超過則出現無法預料的結果,這是它與CString的不同之處。而CString是一個串類,記憶體空間類會自動管理。 CString轉換成LPCWSTR
cocos2d-x型別轉換(CCstring int string char UTF-8互轉)
====================================== string 轉 CCStringstd::string str = "123"; CCString* ns=CCString::createWithFormat("%s",str.c_str()); CCString 轉
JNI資料型別轉換和JNIEnv的介紹、操作jobject,以及jstring的介紹
摘錄、參考文件: 1.深入理解Android:卷1 作者:鄧凡平 上一章,講了關於JNI註冊的相關知識; 這一章講的內容比較多,主要是以下幾方面的內容: 1)java與JNI之間的資料型別轉換; 2)JNIEnv的介紹; 3)JNIEnv的使用,如何操作jobject;
Python 等待鍵盤輸入input(),資料型別轉換int() float() str()
demo.py: # 變數名 = input("提示資訊:") # 等待鍵盤輸入。(輸入的內容都是字串型別) # 1. 輸入蘋果的單價。 price_str = input("蘋果的單價:") # 2. 輸入蘋果的重量 weight_str = input("蘋果的重
專案期複習:JS操作符,彈窗與除錯,凝視,資料型別轉換
1、JS操作符 ① 除法運算後。是有小數存在的。跟C語言不同之處
redis的資料型別List,其原生命令和php操作Redis List函式介紹
List型別介紹 List是簡單的字串列表,按照插入順序排序,可以從列表的兩頭新增資料,一個列表最多可以包含2^32-1個元素(超過40億個元素) List原生命令 命令 命令描述 例項 LPUSH key value1 [va
將資料型別轉換成字串,將字串轉換成資料型別
方法1:採用靜態方法 int a = 123456789; String str = String.valueOf(a); 方法2:用包裝類轉換 float a=2.33f; String str=Float.toString(a); doubl
Object-C 中各資料型別轉換 NSData轉NSString,Byte,UIImage
1,NSData 與 NSString NSData --> NSString NSString *aString = [[NSString alloc] initWithData:adata encoding:NSUTF8StringEncoding]; NSStri
【JavaScript基礎筆記】資料型別轉換、false值、記憶體圖、垃圾回收和深淺拷貝簡易概念
其他型別轉換成字串 xxx.toString() // var object = {a:1}; object.toString = [object Object] //這種方法對null undefined使用會報錯 xxx +
elasticsearch複合資料型別——陣列,物件和巢狀
在ElasticSearch中,使用JSON結構來儲存資料,一個Key/Value對是JSON的一個欄位,而Value可以是基礎資料型別,也可以是陣列,文件(也叫物件),或文件陣列,因此,每個JSON文件都內在地具有層次結構。複合資料型別是指陣列型別,物件型別和巢狀型別,各個
redis的資料型別Set,其原生命令和php操作Redis Set函式介紹
sRandMember(key,[count])函式 * 說明:從集合key中隨機取出一個或count個成員,但是不移除key中的成員 * 引數:key(集合的名稱),count(可選項,隨機取出成員的個數) * 返回值:沒有count引數,返回key中的一個成員(string);存在count,則返回隨機取
python中map()函式使用,資料型別轉換
python中map()函式進行資料轉換 用法: map(function, iterable, …), 返回的是map型,(ps:python2中返回的是list型可以直接顯示,但在python3中是map型無法直接顯示) 引數function: 傳的是一
python之路day03--資料型別分析,轉換,索引切片,str常用操作方法
資料型別整體分析 int :用於計算bool:True False 使用者判斷str:少量資料的儲存list:列表 儲存大量資料 上億資料[1,2,3,'zzy',[aa]]元組:只讀列表(1,23,'asdadas')dist:字典 鍵值對的形式儲存,關係型{'name':'小王八','age':16}