今天在寫 code 用到 GetEnvironmentStrings(),雖然處在 UNICODE 環境下,但我希望使用 ANSI 版本,所以依照 generic-text-mapping 的慣例,直接呼叫 GetEnvironmentStringsA(),沒想到卻怎麼樣也找不到 function declaration。

所謂的 generic-text-mapping 技術,在這篇《Writing libraries with Generic-Text-Mapping》裡有詳細的介紹。簡單講就是,因為不確定呼叫 library 的程式,會是 UNICODE 還是 ANSI 版本,故 library 就兩者皆提供,然後在 header file 裡做特殊處理,選擇真正呼叫的函式版本。因為 header file 是被呼叫端 #include,與呼叫端程式一起編譯的,故利用如下的寫法,就可以選擇真正呼叫的函式版本:

int StrToIntA(const char* str);
int StrToIntW(const wchar_t* str);
#ifdef _UNICODE
#   define StrToInt(str) StrToIntW(str)
#else
#   define StrToInt(str) StrToIntA(str)
#endif

其中,依據 generic-text-mapping 建立的命名慣例,後綴字(postfix)為 W 時,表示是 wchar_t 的版本;後綴字為 A 時,則表示是 ANSI,即 char 的版本。

所以,依照慣例,GetEnvironmentStrings() 應該會有三個版本:

函式原型 版本
TCHAR* GetEnvironmentStrings()  Generic-text-mapping 版
char* GetEnvironmentStringsA()  ANSI 版
wchar_t* GetEnvironmentStringsW()  UNICODE 版

而根據 MSDN 的說明,也是如此:

Implemented as GetEnvironmentStringsW (Unicode) and GetEnvironmentStringsA (ANSI).

只好追蹤原始碼,看看 GetEnvironmentStrings() 是怎麼被宣告的。在 WinBase.h 裡發現,GetEnvironmentStrings() 是這麼被宣告的:

WINBASEAPI
LPSTR
WINAPI
GetEnvironmentStrings(
    VOID
    );

WINBASEAPI
LPWSTR
WINAPI
GetEnvironmentStringsW(
    VOID
    );

#ifdef UNICODE
#define GetEnvironmentStrings  GetEnvironmentStringsW
#else
#define GetEnvironmentStringsA  GetEnvironmentStrings
#endif // !UNICODE

原本 ANSI 版的實作應該是 GetEnvironmentStringsA(),卻變成了 GetEnvironmentStrings(),然後在後面的 macro 裡,又補上將 GetEnvironmentStringsA 對應到 GetEnvironmentStrings()

造成的結果是,如果呼叫端的程式是 ANSI 版,那一切太平,GetEnvironmentStrings()GetEnvironmentStringsA()GetEnvironmentStringsW() 三者皆可以使用。但如果呼叫端的程式是 UNICODE 版,那事情就麻煩了,只有 GetEnvironmentStringsW() 可以使用:

  • 若呼叫 GetEnvironmentStringsA(),會沒有這個函式原型;
  • 若呼叫 GetEnvironmentStrings(),則會被對應到 GetEnvironmentStringsW(),讓接收回傳值到 char* 變數的部份,產生編譯錯誤。

基本上我認為,這應該是 Microsoft 的筆誤,只是我翻遍了 Visual C++ 6、Visual Studio 2005 與 Visual Studio 2008,以及手上有的好幾個 Windows Platform SDK 版本,裡面都含有同樣的錯誤。這樣的問題,可以存活這麼久,還真是不可思議啊。

解決方法?當然是另外用一個獨立的 compilation unit,也就是獨立的 .cpp 檔,包一組 MyGetEnvironmentStrings()MyGetEnvironmentStringsA()MyGetEnvironmentStringsB(),然後特別注意,在這個特別的 compilation unit,使用 ANSI 版本,也就是不要宣告 UNICODE_UNICODE,或在 #include <windows.h> 之前,#undef UNICODE#undef _UNICODE