本系列深入探討 ATL (Active Template Library) 內部實作技術。首篇聚焦 C++ 物件模型核心:虛函數表 (vtable)、虛指標 (vptr)、單一/多重繼承下的記憶體佈局,以及 ATL 如何利用 `offsetofclass` 宏操作介面指標。
class Empty {}; // sizeof = 1 (C++ 標準:非零大小)
class Point { int x, y; }; // sizeof = 8 (兩個 int)
template class Pt { T x, y; }; // Pt sizeof = 8
單一繼承:衍生類別大小 = 基底成員 + 衍生成員
class Point3D : public Point { int z; }; // sizeof = 12 (x,y,z)
記憶體佈局:基底成員在前,衍生成員在後,連續排列。
class Base {
virtual void f() {} // 引入虛函數
};
// sizeof(Base) = 4 (32位) / 8 (64位) — 增加一個 vptr
關鍵發現:vptr 位於物件最前端 (Visual C++ 實作)。
class Point {
int x, y;
virtual ~Point() {}
};
Point pt(5, 10);
int* p = reinterpret_cast(&pt);
p[0] = 100; // 企圖修改 x
p[1] = 200; // 企圖修改 y
// 實際結果:x=200, y=10 → p[0] 是 vptr,p[1] 才是 x
// 正確存取:p[1]=100 (x), p[2]=200 (y)
// 手動呼叫虛函數 (教學用,生產碼切勿如此)
typedef void(*Fun)();
Base obj;
Fun pFun = reinterpret_cast(*reinterpret_cast(&obj))[0];
pFun(); // 呼叫第一個虛函數
class Base1 { virtual void f() {} };
class Base2 { virtual void f() {} };
class Base3 { virtual void f() {} };
class Derived : public Base1, public Base2, public Base3 {
virtual void fd() {}
};
// sizeof(Derived) = 12 (3 個 vptr,各 4 bytes)
記憶體佈局:
Derived 物件: +------------------+ <-- this (Base1*) | Base1::vptr | 指向 Base1 虛表 (含 f, fd) +------------------+ <-- this+4 (Base2*) | Base2::vptr | 指向 Base2 虛表 (含 f) +------------------+ <-- this+8 (Base3*) | Base3::vptr | 指向 Base3 虛表 (含 f) +------------------+
衍生類別新增虛函數 (`fd`) 會加入第一個基底的虛表中 (Primary Base)。
目的:編譯期計算衍生類別中指定基底子物件的位移。
#define _ATL_PACKING 8
#define offsetofclass(base, derived) \
((DWORD)(static_cast ((derived*)_ATL_PACKING)) - _ATL_PACKING)
// 使用範例
class Base1 { virtual void f() {} };
class Base2 { virtual void f() {} };
class Derived : public Base1, public Base2 {};
offsetofclass(Base1, Derived) // = 0
offsetofclass(Base2, Derived) // = 4 (32位)
原理:將非零常數 (`_ATL_PACKING`) 轉為 `derived*`,`static_cast` 到 `base*` 會依 C++ 標準調整指標位移,相減即得位移量。
#define BASE_OFFSET(ClassName, BaseName) \
(DWORD)(static_cast(reinterpret_cast(0x10000000)) - 0x10000000)
效果相同,使用較大常數避免對齊干擾。
Derived d;
void* pBase1 = static_cast(&d) + offsetofclass(Base1, Derived);
static_cast(pBase1)->f(); // 呼叫 Base1::f()
void* pBase2 = static_cast(&d) + offsetofclass(Base2, Derived);
static_cast(pBase2)->f(); // 呼叫 Base2::f()
這正是 ATL `CComPtr`、`_ATL_INTMAP` 等機制查詢介面的核心邏輯。
將探討 ATL 如何實作 `QueryInterface`、物件對應表 (`_ATL_OBJMAP`)、模組類別 (`CComModule`) 等核心架構。