1. 程式人生 > >資料型別轉換,寬位元組和單位元組互轉

資料型別轉換,寬位元組和單位元組互轉

以前一直使用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中的BigDecimalString的相互轉換intString的型別轉換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 中各資料型別轉換 NSDataNSStringByteUIImage

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}