COM中的字符串

 

   這兩天被BSTR折騰了不行,特從網上搜來,做資料查閱

字符串是一個文本類型數據(字符)的數組,我們要處理一個字符串需要考慮這麼幾點:

第一,   字符類型。不同的操作系統,或者統一操作系統內會有不同的字符類型比如說,, char, unsigned char, wchar類型。

第二,   和字符集不同。如UNICODE, SBCS(ANSI), MBCS, DBCS等不同的字符集。而且統一字符集下會有不同代碼頁(CODE PAGE)。

第三,   如何決定字符串長度。如有的語言把字符串中的NUL作爲字符串結尾,而另外一些則在字符串首位標定該字符串的長度。

第四,   字符串相關的資源管理問題。由於長度一般不同,所以經常會爲字符串動態分配內。而這些內存的收回由於所有權不同而不同,不同的語言處理方式也不一樣。

所有這些不同會使得字符串的處理也不盡相同。

Windows平臺內本身就有好幾種字符集,相應用不同的字符類型來表示。

n         UNICODE       wchar, wchar*

16位的多語言字符集。

n         MBCS/DBCS unsigned char, unsigned char*

混合長度的字符集。有的字符用一個字節表示,而有些字符用幾個字節表示。VC中他們是等效的。

n         ANSI(SBCS) char, char*

面對這麼做紛繁複雜的情況,我們比較頭疼。希望我們在程序內部用一種統一的字符集進行編碼。同時,爲了讓軟件發佈在不同操作系統下,我們最好能在編譯的時候來決定具體使用哪一種編碼。

根據這一要求Microsoft定義了一種統一的數據類型TCHAR/_TCHAR來編寫統一的代碼。我們在編碼的時候統一使用這種數據類型,然後在編譯的時候來更具編譯選項來確定目標代碼所用的字符集。爲此,Microsoft在C運行庫裏添加了tchar.h以支持TCHAR及其處理函數。WIN32環境內的Microsoft在winnt.h中也定義了TCHAR及其處理函數。同時,有相應的一整套TCHAR字符串處理函數。

另外對於每個需要字符串的win32 API都有兩個版本xxxxA和xxxxW,他們會通過編譯宏來進行切換。

使用方法:

確認是否包含了WINNT.H。一般Wizard生成的文件中都已經包含了該文件;

在程序內嚴格使用TCHAR/LPTSTR/LPCTSTR來表示字符/字符串;

每個文字或者字符串都要用宏_TEXT/__TEXT/_T/__T包起來;

處理字符串的時候用_tcsXXX(同strXXX, wcsXXX相對應)函數集。

使用兩個預處理符號進行編譯_UNICODE/UNICODE, _MBCS來確定TCHAR和內在字符類型之間的映射。UNICODE: TCHAR->wchar, _MBCS: TCHAR->char

       如果編譯時定義了_MBCS符號,則所有的TCHAR字符被映射成char字符,並且預處理器會去掉所有的_T和_TEXT宏的變種,不改變字符或字符串(分別創建一個MBCS字符或字符串)

       若編譯時未定義任何與處理器符號,則所有的TCHAR字符會被映射成char字符,並且預處理器會去掉所有的_T和_TEXT宏的變種,不改變字符或字符串(分別創建一個ANSI字符或字符串)

COM字符串類型

COM是一個語言中立硬件結構中立的模型。因此,它需要一個語言中立、硬件結構中立的文本數據類型。由於不同的平臺使用的字符類型不同,所以很難有一種類型能使和COM的需要。所以COM就定義了一種文本數據類型——OLECHAR。在大多數平臺上,包括WIN32平臺都把OLECHAR解釋成wchar,而在一些16位操作系統下,如win95, Macintonsh OS, OLECHAR會被解釋成CHAR。

當我們使用C/C++定義接口的方法傳遞字符串的時候或者從接口接受一個字符串的時候,經常會用OLECHAR*,在構建一個OLECHAR*的時候需要用宏OLESTR()把字符串包起來。但是如果我們使用其它語言定義的接口時,字符串經常是BSTR類型。

對於BSTR,他實際上就是OLECHAR*,不過他還有一些特殊的含義

n         BSTR指向的數組的前綴是該字符串的長度。

n         字符數組是以NUL字符作爲結尾。

n         紀錄的長度是字節爲單位,而不是字符,並且不包括NUL終止符。

n         字符數組內部可以包含嵌入的NUL字符。

n         他必須用SysAllocString和SysFreeString函數來分配和釋放。

n         NULL BSTR意味着一個空字符串。

n         複製BSTR意味着製作字符串的一個拷貝,不是簡單的複製指針。

注意:BSTR的前綴是BSTR指向數組的前邊四個字節。這段內存會被::SysStringxxxx管理。所以如果把BSTR當作OLECHAR*有時是可以用的,因爲他們指向一個字符串數組。但是,OLECHAR*會以NUL爲結尾,會把內嵌NUL的BSTR截斷。反過來,我們不能把OLECHAR*看作BSTR,雖然他們都指向一個字符數組,但是他前邊的四個字節的內容是無效的,也就是BSTR的長度無效。這個時候讓::SysStringxxxx來管理他的話會出問題。

字符串的轉換

即使我們在程序內部都進行了統一編碼。但是,如果涉及到對第三方程序的應用,不可避免的要引起字符處理的混亂。我們希望能夠把這些涉外的字符串和程序內部的字符串進行轉換。這樣可以維護程序的一致性。

ATL提供了一系列的轉換宏,在必要的時候他可以在前邊提到的字符類型之間進行轉換。這些宏的命名使用“<源類型縮寫>2<目標類型縮寫>”的形式。具體縮寫如下所示:

T           TCHAR類型指針——LPTSTR

W          Unicode wchar類型指針——LPWSTR

A            MBCS/ANSI char類型指針——LPSTR

OLE        COM OLECHAR類型的指針——LPOLESTR

BSTR       COM BSTR類型字符串。

C            C/C++中的const修飾符

在宏轉換的時候,不可避免的要產生一些臨時變量(如字符串長度),這些變量會在棧上分配。如果我們在一個循環內進行這些轉換,就會有很多臨時變量產生而不能得到及時的釋放。所以我們定義一個宏USES_CONVERSION(只需要一次)來紀錄這些臨時變量,從而大大節約了內存。

 

輔助函數

當我們在編寫一個WIN32的組件時執行了一個操作——複製OLECHAR字符串。由於在WIN32系統上OLECHAR實際上是wchar所以,在WINNT上是通過lstrcpyW來實現的。但是如果把這個組件放在win95上,由於他沒有實現lstrcpyW,所以程序調用就會出問題。同樣涉及到OLECHAR字符串的長度獲取以及字符便利等等。在這裏ATL提供了

OLECHAR* ocscpy (LPOLESTR dest, LPCOLESTR src);

size_t ocslen(LPOLESTR s);

LPOLESTR CharNextO(LPCOLESTR lp);

三個函數,使得這些操作在NT和95下都能正確運行。

心得

       對於COM內部統一使用TCHAR/LPTSTR來說是很容易理解的。需要明確的一點就是TCHAR的映射問題。我們建立組件的時候應根據特定的操作系統來使用最優的字符串處理方法。對於WIN NT我們一般選用UNICODE編譯選項,但是編譯結果卻不能在WIN9X下正確運行(WIN9X不支持UNICODE)。所以在不涉及到OLECHAR的情況下爲了使的構建出來的組件即能在NT下運行也能在WIN9X下運行我們一般選用_MBCS編譯選項。

       但是,如果涉及到了OLECHAR,問題就比較複雜了。因爲在一般情況OLECHAR都當作wchar,如果我們無法迴避對OLECHAR*的直接操作,就必須使用上邊提到的一些輔助函數。如果這些輔助函數不夠用,從而使用了wchar的相應函數,那麼在win9x下就會出現運行錯誤。

       以上說的都是win32平臺上的應用。如果這個組件要在16位操作系統下運行,那麼就需要注意OLECHAR的映射。由於在16位操作系統下一個OLECHAR就是一個char,所以在WIN32平臺下如果沒有添加_OLE2ANSI選項,編譯出來的組件(有涉及到OLECHAR的接口)就根本不能用,反過來,在16操作系統下編譯出來的組件也不能在32位操作系統下運行。這些問題是的我們發佈一個“萬能”組建變得很困難。所以,還是根據不同的目標操作系統進行編譯、發佈。這樣反而利用了這些操作系統的優點。       還有一個問題就是OLECHAR*,和BSTR的關係了。我們前邊也介紹了他們的區別和聯繫。我想組件是一個語言中立的結構,所以儘量能讓他不被一種語言限定。而OLECHAR*作爲字符串只能C/C++的語言環境下使用,限制了組件的應用範圍。故而還是儘量使用BSTR進行字符串的操作。

從前邊的敘述中可以看出BSTR的含義很豐富,限制也比較多。這樣我們在使用過程中就得很小心。實際編程過程中,BSTR的應用又很頻繁。爲了避免一些不必要的麻煩,ATL提供了一個類把他包裝起來,使得我們不必過多的關心他的細節。

CComBSTR把一個BSTR作爲成員變量,通過各種方法對他進行維護。

以下是該類的具體內容及其注意事項:

 

構造函數和析構函數

Ø         CComBSTR(); m_str = NULL;

BSTR爲NULL的時候,有時候同於一個指向控字符串””的指針。如VB中IF “”=P的返回值會爲真。但是,很多BSTR相關的WIN32 API不一定會有此認同,他們要求被操作的BSTR必須不爲空。如SysStringLen

Ø         CComBSTR(LPOLESTR pSrc);

不停的複製字符直到NUL終止符。

Ø         CComBSTR(int size, LPOLESTR pSrc);

首先創建一個size大小的BSTR,然後copy pSrc中複製包括NUL在內的任何字符,然後加一個NUL作後綴。如果pSrc爲空,則構造一個大小爲size的沒有初始化的BSTR。

Ø         CComBSTR(int size);

則構造一個大小爲size的沒有初始化的BSTR

Ø         CComBSTR(const CComBSTR src)

調用了對象的Copy方法。

由於Copy是更具自己的長度構造的一個拷貝,所以能夠正確複製(包括內嵌NUL字符)

Ø         #ifndef OLE2ANSI

CComBSTR(LPCSTR pSrc);

CComBSTR(int size, LPCSTR pSrc);

#endif

這兩個函數同上邊的兩個函數基本一致,不同的是,他們不能有內嵌NUL。而且當定義了OLE2ANSI的時候LPCSTR = LPOLESTR,下邊的函數就不能再有了。

Ø         CComBSTR(REFGUID src)

生成一個包含GUID的字符串。

       析構函數~ CComBSTR() {::SysFreeString(m_str)}

       應爲他會自動釋放BSTR,所以在處理異常處理的時候非常有用。

       注意,內嵌NUL的字符串。不是說只有按照BSTR的含義去理解LPOLESTR才能得到內嵌NUL的字符串。因爲LPOLESTR實際上是一個OLECHAR的數組,他的長度有數組的性質來決定,所以在NUL後邊還可以有其他內容。只是我們把它僅僅理解城LPOLESTR的時候,他的有效長度僅僅是第一個NUL之前的長度。所以會出現CComBSTR(int size, LPOLESTR pSrc)來構造內嵌NUL的字符串。但是這樣的數組絕對不是BSTR,因爲BSTR的首字節記錄了該串的長度。

 

初始化

CComBSTR& operator=(const CComBSTR& src)

CComBSTR& operator=(LPCOLESTR pSrc)

   #ifndef OLE2ANSI

        CComBSTR& operator=(LPCSTR pSrc)

#endif    

bool LoadString(HINSTANCE hInst, UINT nID)

bool LoadString(UINT nID)

這三個初始化函數都是先把m_str原來所指的BSTR釋放,然後根據參數進行初始化。第一個初始化函數能夠準確的(包含NUL字符)製造一個副本。而後邊兩個只能按照以NUL結尾的字符串進行初始化。第四個是根據hInst資源句柄去加載字符串,而最後一個其實也一樣,只不過資源句柄是一個全局變量_pModule->m_hInstResource,_pModule在服務器對象初始化的時候確定(在DllMain或WinMain中調用CComModule::Init)。

 

操作符

Ø         operator BSTR() const

當顯式地或者隱式地把一個CComBSTR對象強制轉換成一個BSTR對象的時候就會調用這個方法

Ø         BSTR* operator&()

注意這個方法返回的是內部變量的地址,如果我們在改變這個變量之前沒有釋放原來的字符串會導致內存泄漏。但是,用它來接收一個BSTR的時候很有用,我們這樣可以直接用CComBSTR來管理BSTR的生命週期。

HRESULT get_Name(/*out*/BSTR* pName);

CComBSTR bstrName;

Get_Name(bstrName);

Ø         HRESULT CopyTo(BSTR* pbstr)

精確地構造一個副本,用它來給調用這構造返回值比較合適。注意,我們需要顯式地式方pbstr。

Ø         BSTR Detach()

他返回了m_str,而且把自己清空,如果利用Copy的話就會構造一個副本,效率比較低。CComBSTR析構的時候不會做任何事情。但是,我們需要顯式地式方它的返回值。

Ø         void Attach(BSTR src)

接收一個BSTR用CComBSTR來維護他的生命週期。如果把一個BSTR附加在一個非空的CComBSTR上,就會產生內存泄漏。

Ø         void Empty()

釋放內存並且清空CComBSTR。

Ø         一個非空CComBSTR調用Attach之前一般調用Empty()。

 

連接字符

HRESULT Append() 4個

HRESULT AppendBSTR(BSTR p)

CComBSTR& operator+=(const CComBSTR& bstrSrc)

當參數是BSTR或者CComBSTR的時候會附加整個BSTR,但如果是LPCOLECHAR或LPCSTR會添以NUL結尾的字符串。

 

字符大小寫轉換

ToLower ToUpper。實現的時候總是OLE2T->XXX->T2OLE。所以一般是由損失的,因爲專成TCHAR字符串的時候會議NUL終止。內嵌NUL的字符串會出問題。

 

比較操作

bool operator!() const

bool operator<(BSTR bstrSrc) const

bool operator<(LPCSTR pszSrc) const

bool operator==(BSTR bstrSrc) const

bool operator==(LPCSTR pszSrc) const

在比較參數爲BSTR的時候,內部會用wcscmp來比較,所以在字符串轉換的時候會因爲NUL造成損失。

 

對永久性的支持。

HRESULT WriteToStream(IStream* pStream)

他把表示BSTR長度(字節爲單位SysStringByteLen)的ULONG計數值寫入流中。緊接着寫入BSTR。

HRESULT ReadFromStream(IStream* pStream)

他是上一過程的相反,但是如果一個非空的CComBSTR調用該函數是事先不會清空,會產生內存泄漏。

心得:

       首先就是OLECHAR*和BSTR的區別和聯繫。編譯器認定他們是相同的,但是他們確確實實不同。使用的時候應該非常注意纔對。

       另外就是涉及到內嵌NUL的字符串的處理。CComBSTR做的並不完美。

還有就是內存問題。自己時刻注意什麼時候應該釋放內存。

轉自:http://zhikangs.spaces.live.com/blog/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章