使用ATL宏USES_CONVERSION(轉載)

這兒是個關於宏的問題,我曾用過ATL的串轉換宏,包括W2A,開始有些東西我還不太明白。爲了使用這些宏,必須在函數的開始處用USES_CONVERSION來初始化某些局部變量。用就用吧,但是看看這個宏的定義,它有類似下面的代碼:

// 在atlconv.h文件中
#define USES_CONVERSION
int _convert; _convert;
UINT _acp = GetACP(); _acp;
LPCWSTR _lpw; _lpw;  
LPCSTR _lpa; _lpa

爲什麼它們用“int x;x;”——這種後面跟着變量的聲明? 

    很多人都碰到過這個令人困惑的問題,後來發現簡單的答案是:禁止編譯器的警告信息(warning)。如果單獨有一行代碼:
int x;
且從來沒有使用過x,那麼編譯器彙報錯“unreferenced local variable:x”,意思是未引用過的局部變量x,如果將警告信息的輸出調到最大。爲了避免討厭的警告,USES_CONVERSION引用聲明的變量。

int x; // 聲明
x; // 使用這個變量

在C++之前的時代,程序員有時在C中用函數形參做同樣的事情來避免“unreferenced formal parameter”或其它的深奧費解的編譯錯誤。

void MyFunc(int x, char y)
{
x;
y;

}

當然,現在用下面的代碼可以更有效地完成同樣的事情:

// 參數 x 不是用
void MyFunc(int /* x */) 
{

}

也 就是說聲明參數,但不給它起名,不能這樣使用局部變量;必須顯式地引用它。這樣做不會增加任何指令到代碼中。最多可能多增加幾個字節到堆棧(爲x預留空 間)。靈巧的編譯器甚至不會操心x從來沒有被使用過——雖然好奇心可能想知道:如果編譯器夠厲害,知道了從沒有使用x,爲什麼要抱怨(編譯出錯)呢?答案 是因爲苛刻的程序員(且編程能力與個人的記性有關)使用編譯警告提醒自己刪除某部分代碼時發生變量荒廢。這種警告在C時代很有用,你必須在每個函數的頂部 聲明變量,這就遠離了實用它們的代碼。
現在來看看另一個問題:爲什麼在開始位置要用USES_CONVERSION?即爲什麼W2A&Co之類的宏還需要另外的宏聲明自己的變量;爲什麼不直接在W2A中聲明這個變量?
#define W2A(x)
int _convert; _convert;
……etc

很明顯這樣做不行,因爲如果你使用W2A兩次,得到一個複製的變量。那爲什麼不把整個宏放進花括弧創建新的範圍?

#define W2A(x) Q{
int _convert; _convert;
……
}

這樣解決了命名衝突,但不能進行如下編碼:
DoSomething(W2A(pwstr));
沒有辦法從代碼塊返回值,所以不能在函數調用中傳遞W2A。真笨啊,那麼內聯函數怎麼樣?

inline LPCSTR W2A(LPWSTR w) {
int_convert;
……
}

這解決了範圍問題——任何W2A需要的變量多可以在這個函數中,在自己的範圍內聲明,不需要另外的宏。它也提供了一種返回值的方式,使你可以在函數調用和賦值中使用W2A(x)。但是這種方法不靈,也是因爲W2A的緣故和其它的宏更復雜。
不管什麼時候進行Unicode轉換,都不能就地轉換串,必須分配一個臨時串容納被轉換的字節。典型地,通過調用new分配一個串:

int len = MultiByteToWideChar(..., 
mystr, NULL, 0); // 或的長度
LPWSTR p = new WCHAR[nLen]; // 分配內存
MultiByteToWideChar(...,p,len); // 轉換
SomeCOMFunction(p); // 使用之
delete [] p; // 銷燬
這 段代碼不僅令人討厭,而且還沒有效率;必須調用MultiByteToWideChar兩次(一次是計算長度,一次是實際的轉換),你得從堆中分配p,這 樣很慢。通過分配2*len個字節解決第一個問題,這裏長度len是ASCII串的長度——但第二個問題怎麼辦?如果看看A2W是如何展開的,請看:

// 簡化版
#define A2W(s)
_len = 2*strlen(s);
AfxA2WHelper((LPWSTR)alloca(_len);

AfxA2Whelper 是一個調用MultiByteToWideChar的輔助函數。A2W使用2*len巧妙地避免了兩次調用MultiByteToWideChar。但 A2W及其它轉換宏真正聰明的地方是不調用new操作分配臨時串,而是調用alloca——在棧中分配字節,而不是在堆中。這樣做非常快,因爲編譯器要做 的只是增加棧指針。不調用函數,不處理內存塊。它也避免了內存碎片,並且也沒有必要調用delete操作,因爲當控制離開alloca被調用的地址後,內 存被自動釋放。這正好說明了爲什麼A2W不能時內聯函數;如果是的話,alloca創建的臨時串會在返回前被摧毀,並且你會以刪除串的方式終止 SomeCOMFunction(使用這個例子)調用。
A2W必須從alloca被調用的相同的地址處調用alloca——所以A2W必須是一個 宏,不是一個函數;因此它需要另一個宏USERS_CONVERSION來聲明_len以及其它一些用到的變量(爲了簡化,我省略了)。當你仔細想想,整 個處理告訴我們要想寫一組類似A2W的宏從棧中的分配內存會減少很多不必要的麻煩。
另外,任何時候,只要你想要快速地獲取臨時內存,都可以調用alloca。下面的代碼是我們常常見到的:

char* p = new char[len];
DoSomething(p);
delete [] p;

使用下面的代碼替代之會效率更高:

char *p = (char*)alloca(len);
DoSomething(p);
// 不用調用delete p!

當然,如果棧中的沒那麼多,你還這麼做的話,就會出現一個討厭的消息框。這個方法還有一些侷限,詳細內容請參考文檔。經驗告訴我,不管什麼時候在MFC、ATL或其它什麼地方發現奇怪的事情,最好是鑽進去研究一下,你可能會發現有用的東西。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章