ATL 為了極致的效能與體積,大量使用內聯組語、裸函數 (__declspec(naked)) 及自訂呼叫慣例。本文從呼叫慣例、堆疊幀建立、函數前序/尾序代碼、naked 屬性等底層機制切入,層層剖析 ATL 源碼中常見的組語模式。
void fun(int a, int b) {}
// 呼叫 fun(5, 10) 產生組語:
push 10
push 5
call fun
add esp, 8 (2 參數 × 4 位元組)。printf)。_fun。ret 8。_fun@8 (@ 後接參數總位元組數)。push ebp
mov ebp, esp
sub esp, N ; 配置區域變數空間
EBP 為基址指標,指向目前堆疊幀底部。[EBP+8], [EBP+12]... (高位址)。[EBP-4], [EBP-8]... (低位址)。[EBP+4],呼叫者 EBP 在 [EBP]。mov esp, ebp
pop ebp
ret N ; __stdcall 才有 N,__cdecl 為 ret 0
extern "C" int __stdcall fun(int a, int b) {
_asm mov dword ptr [ebp+12], 15 // 修改 b 為 15
_asm mov dword ptr [ebp+8], 14 // 修改 a 為 14
return a + b; // 實際回傳 29
}
驗證呼叫慣例差異:__cdecl 需呼叫者 add esp, 8;__stdcall 由 ret 8 清理。
return (需手動 mov eax, ...; ret)。extern "C" int __declspec(naked) sum(int a, int b) {
// 手寫 Prolog
_asm push ebp
_asm mov ebp, esp
// 運算並放入 EAX (返回值慣例)
_asm mov eax, dword ptr [ebp + 8]
_asm add eax, dword ptr [ebp + 12]
// 手寫 Epilog
_asm pop ebp
_asm ret 8 // __stdcall 清理 2 參數
}
在 ATLBASE.H 中,_QIThunk 結構用於 _ATL_DEBUG_INTERFACES 追蹤介面查詢與參考計數。其成員函數標記 __declspec(naked),避免額外呼叫開銷,直接在組語層級記錄 QueryInterface、AddRef, Release 呼叫堆疊。
Thunk 結構必須無位元組對齊 (#pragma pack(push, 1)),確保機器碼緊密排列,位移計算正確。範例:
struct S { char ch; int i; }; // 預設對齊,大小 8
#pragma pack(push, 1)
struct S1 { char ch; int i; }; // 緊湊,大小 5
#pragma pack(pop)
掌握呼叫慣例、堆疊幀佈局、naked 函數限制與內聯組語,是閱讀、除錯、擴充 ATL 核心程式碼的基礎。下一篇將探討 ATL 的模板元編程技術:策略模式、智慧指標、模擬虛函數等進階樣板應用。