讀者來信指正專欄中使用 WM_USER+1 與對話框訊息 DM_SETDEFID 衝突,並詢問 WM_APP 與註冊訊息的適用場景;另有讀者探討 C++/CLI 中 S"..." 托管字串字面值與傳統 _T("...") 的效能差異;還有讀者嘗試用 #pragma unmanaged 保護演算法知識產權。本期專欄逐一深度解答。
圖 1 總結 Windows 訊息值正式分類(每十年必複習):
| 範圍 | 用途 |
|---|---|
0 ~ WM_USER-1 (0x03FF) | 系統保留(WM_CREATE 等) |
WM_USER ~ 0x7FFF | 私有視窗類別專用(如狀態列 SB_SETTEXTA = WM_USER+1,對話框 DM_SETDEFID = WM_USER+1) |
WM_APP (0x8000) ~ 0xBFFF | 應用程式自定義訊息,不與系統/控件衝突(Win95/NT 4.0+ 支援) |
| 0xC000 ~ 0xFFFF | 註冊訊息(RegisterWindowMessage),跨進程全域唯一 |
錯誤修正:專欄中用 WM_USER+1 確實與 DM_SETDEFID 衝突。CMyOpenDlg 作為可複用擴展類別,宜用 WM_USER+10 等較大偏移避開標準控件訊息,或視情況採用 WM_APP / 註冊訊息。
WM_APP vs 註冊訊息:如何選擇?RegisterWindowMessage(_T("WmSayHello")) 取得全域唯一值,同一會話中所有註冊者獲得相同值。S"..." 為何更快?編寫測試程式 StrLit.cpp,對比 IL 代碼(ILDASM 反組譯):
_T("...")// String* c2 = new String(_T("This is a TCHAR string"));
ldsflda valuetype $ArrayType$... '?A0x...unnamed-global-0'
newobj instance void [mscorlib]System.String::.ctor(int8*)
$ArrayType$),呼叫 String::.ctor(int8*) 建構函數。S"..."// String* s1 = new String(S"This is a String literal");
ldstr "This is a String literal"
ldstr 指令,直接從中繼資料推入**已駐留**的字串物件參考。| 方式 | 耗時 | 記憶體 | 實際分配物件數 |
|---|---|---|---|
_T("foo") | 32,797 ms | 190 KB | 5000 萬(GC 疯狂回收) |
S"foo" | 100 ms | 43 KB | 1(全複用同一物件) |
結論:S"..." 不僅避免建構函數呼叫,更因字串駐留機制極大降低配置/GC 開銷。凡需傳遞 System.String 的托管 API,優先使用 S"..."。
wchar_t),無自動轉換至 TCHAR/char*。vcclr.h 提供的 PtrToStringChars 取得固定指標:const WCHAR __pin* p = PtrToStringChars(str);#pragma unmanaged 實戰/clr 編譯預設產生 MSIL。若要隱藏核心演算法:
#pragma unmanaged 將核心函數編譯為原生機器碼。__gc class、String^ 等)。__gc 類別方法負責參數封送(如 PtrToStringChars),再呼叫原生函數。#pragma unmanaged
void DoSecretAlgorithm(const WCHAR* pstr) { ... } // 純原生實作
#pragma managed
__gc class Widget {
public:
void MyMethod(String* s) {
const WCHAR __pin* pstr = PtrToStringChars(s);
DoSecretAlgorithm(pstr); // 呼叫原生核心
}
};
注意:含 __asm、setjmp/longjmp、可變參數的函數會被編譯器**強制**編譯為原生碼。
/clr 隱含 /MT(多執行緒 CRT 靜態連結),確保與 CRT 靜態連結,因 GC 終結器在獨立執行緒運行。呼叫 printf、memset 等 CRT 函數時會自動從靜態庫鏈結。
C++/CLI 是微軟唯一允許單一檔案混合原生碼與 MSIL 的語言,善用此特性可兼顧效能與安全性。