ATL offsetofclass 的工作原理
?ATL深入淺出[1]
作者:ErIM Creator
日期:2007-7-20
參考:http://www.codeproject.com/atl/atl_underthehood_.asp
在這一系列的文章里,我將和大家一起討論ATL的底層工作和ATL所使用到的技術(shù)。如果你只想嘗試寫(xiě)個(gè)普通的ATL控件,這篇文章對(duì)你一點(diǎn)幫助
都沒(méi)有;如果你想更好的學(xué)會(huì)使用ATL,認(rèn)真往下看把。
我們先來(lái)討論一個(gè)類(Class)的存儲(chǔ)配置。首先來(lái)寫(xiě)一個(gè)類,這個(gè)類沒(méi)有任何數(shù)據(jù)成員,然后來(lái)看看他的內(nèi)存結(jié)構(gòu)。
程序1:
#include <iostream>
using namespace std;
class Class{
};
int main()
{
?Class objClass;
?
?cout << "Size of object is = " << sizeof(Class) << endl;
?cout << "Address of object is = " << &objClass << endl;
?return 0;
}
程序輸出:
Size of object is = 1
Address of object is = 0012FF7C
現(xiàn)在,如果我們往類里添加數(shù)據(jù)成員,他的大小會(huì)將等于所有數(shù)據(jù)成員所占內(nèi)存的和。在模版類(template class)中,也是這樣。現(xiàn)在,讓我
們來(lái)看看模版類Point。
程序2:
#include <iostream>
using namespace std;
template <typename T>
class CPoint{
?T m_x;
?T m_y;
};
int main()
{
?CPoint<int> objPoint;
?
?cout << "Size of object is = " << sizeof(objPoint) << endl;
?cout << "Address of object is = " << &objPoint << endl;
?return 0;
}
程序輸出:
Size of object is = 8
Address of object is = 0012FF78
現(xiàn)在,我們也添加一個(gè)繼承類到這個(gè)程序,我們用Point3D類來(lái)繼承Point類,同時(shí)來(lái)看看程序的內(nèi)存結(jié)構(gòu)。
程序3:
#include <iostream>
using namespace std;
template <typename T>
class CPoint{
?T m_x;
?T m_y;
};
template <typename T>
class CPoint3D : public CPoint<T>
{
?T m_z;
};
int main()
{
?CPoint<int> objPoint;
?
?cout << "Size of Point is = " << sizeof(objPoint) << endl;
?cout << "Address of Point is = " << &objPoint << endl;
?CPoint3D<int> objPoint3D;
?cout << "Size of Point3D is = " << sizeof(objPoint3D) << endl;
?cout << "Address of Point3D is = " << &objPoint3D << endl;
?
?return 0;
}
程序輸出:
Size of Point is = 8
Address of Point is = 0012FF78
Size of Point3D is = 12
Address of Point3D is = 0012FF6C
這個(gè)程序說(shuō)明了派生類的內(nèi)存結(jié)構(gòu)。派生類所占用的內(nèi)存總數(shù)是基類的所有數(shù)據(jù)成員和該派生類所有數(shù)據(jù)成員所占內(nèi)存的和。
如果把虛函數(shù)也添加進(jìn)來(lái),那這個(gè)問(wèn)題就更有趣了。我們來(lái)看看這個(gè)程序。
程序4:
#include <iostream>
using namespace std;
class Class
{
?virtual void fun()
?{
??cout << "Class::fun" << endl;
?}
};
void main()
{
?Class objClass;
?cout << "size of class = " << sizeof(objClass) << endl;
?cout << "Address of class = " << & objClass << endl;
}
程序輸出:
size of class = 4
Address of class = 0012FF7C
如果我們添加一個(gè)以上的需函數(shù),那么情況會(huì)變得更有去。
程序5:
#include <iostream>
using namespace std;
class Class
{
?virtual void fun1()
?{
??cout << "Class::fun" << endl;
?}
?virtual void fun2()
?{
??cout << "Class::fun2" << endl;
?}
?virtual void fun3()
?{
??cout << "Class::fun3" << endl;
?}
};
void main()
{
?Class objClass;
?cout << "size of class = " << sizeof(objClass) << endl;
?cout << "Address of class = " << & objClass << endl;
}
程序的輸出和上面一樣。我們來(lái)做多點(diǎn)實(shí)驗(yàn),使我們能更好地理解他。
程序6:
#include <iostream>
using namespace std;
class CPoint
{
?int m_x;
?int m_y;
public:
?virtual ~CPoint(){
?};
};
void main()
{
?CPoint objPoint;
?cout << "size of Point = " << sizeof(objPoint) << endl;
?cout << "Address of Point = " << &objPoint << endl;
}
程序輸出:
size of Point = 12
Address of Point = 0012FF68
這個(gè)程序的輸出告訴我們,無(wú)論你在類里添加多少個(gè)虛函數(shù),他的大小只增加一個(gè)int數(shù)據(jù)類型所占的內(nèi)存空間,例如在Visual C++ 他增加4字
節(jié)。他表示有三塊內(nèi)存給這個(gè)類的整數(shù),一個(gè)給m_x,一個(gè)給m_y,還有一個(gè)用來(lái)處理被調(diào)用的虛函數(shù)的虛指針。首先來(lái)看一看新的一塊內(nèi)存,也
就是虛函數(shù)指針?biāo)純?nèi)存,在該對(duì)象的最開(kāi)始頭(或者最后)。我們可以通過(guò)直接訪問(wèn)對(duì)象所占的內(nèi)存塊來(lái)調(diào)用虛函數(shù)。只要把對(duì)象的地址保
存到一個(gè)int類型指針,然后使用指針?biāo)惴ǖ哪g(shù)(The magic of pointer arithmetic)就能夠調(diào)用他了。
程序7:
#include <iostream>
using namespace std;
class CPoint {
?
public:
?
?int m_ix;
?int m_iy;
?CPoint(const int p_ix = 0, const int p_iy = 0) :
?m_ix(p_ix), m_iy(p_iy) {
?}
?int getX() const {
??return m_ix;
?}
?int getY() const {
??return m_iy;
?}
?virtual ~CPoint() { };
};
int main() {
?
?CPoint objPoint(5, 10);
?int* pInt = (int*)&objPoint;
?*(pInt+0) = 100; // 打算改變 x 的值
?*(pInt+1) = 200; // 打算改變 y 的值
?cout << "X = " << objPoint.getX() << endl;
?cout << "Y = " << objPoint.getY() << endl;
?return 0;
}
這個(gè)程序最重要的地方是:
int* pInt = (int*)&objPoint;
*(pInt+0) = 100; // 打算改變 x 的值
*(pInt+1) = 200; // 打算改變 y 的值
這里我們把對(duì)象的地址保存到一個(gè)int類型的指針,然后把它當(dāng)成整型指針。
程序輸出:
X = 200
Y = 10
當(dāng)然,這不是我們想要的結(jié)果!程序說(shuō)明,這時(shí)的200是保存到x_ix,而不是x_iy。也就是說(shuō)對(duì)象第一個(gè)數(shù)據(jù)成員是從內(nèi)存的第二個(gè)地址開(kāi)始的
,而不是第一個(gè)。換句話說(shuō),第一個(gè)內(nèi)存地址保存的是虛函數(shù)的地址,然后其他保存的都是類的數(shù)據(jù)成員。我們來(lái)改一下下面兩行代碼。
int* pInt = (int*)&objPoint;
*(pInt+1) = 100; // 打算改變 x 的值
*(pInt+2) = 200; // 打算改變 y 的值
這時(shí)我們得到了所期待的結(jié)果。
程序8:
#include <iostream>
using namespace std;
class CPoint {
public:
???????? int m_ix;
???????? int m_iy;
???????? CPoint(const int p_ix = 0, const int p_iy = 0) :
???????????????? m_ix(p_ix), m_iy(p_iy) {
???????? }
???????? int getX() const {
???????????????? return m_ix;
???????? }
???????? int getY() const {
???????????????? return m_iy;
???????? }
???????? virtual ~CPoint() { };
};
int main() {
???????? CPoint objPoint(5, 10);
???????? int* pInt = (int*)&objPoint;
???????? *(pInt+1) = 100; // 想要改變 x 的值
???????? *(pInt+2) = 200; // 想要改變 y 的值
???????? cout << "X = " << objPoint.getX() << endl;
???????? cout << "Y = " << objPoint.getY() << endl;
???????? return 0;
}
程序的輸出:
X = 100
Y = 200
這里很明確的告訴我們,無(wú)論什么時(shí)候我們添加虛函數(shù)到類里面,虛指針都存放在內(nèi)存的第一個(gè)位置。
現(xiàn)在問(wèn)題出現(xiàn)了:虛指針里存放的是什么?來(lái)看看以下程序,我們就能知道它是什么概念。
程序9:
#include <iostream>
using namespace std;
class Class {
???????? virtual void fun() { cout << "Class::fun" << endl; }
};
int main() {
???????? Class objClass;
???????? cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
???????? cout << "Value at virtual pointer " << (int*)*(int*)(&objClass+0) << endl;
???????? return 0;
}
程序輸出:
Address of virtual pointer 0012FF7C
Value at virtual pointer 0046C060
虛指針保存一張叫做虛表的地址。而虛表保存著整個(gè)類的虛函數(shù)地址。換句話說(shuō),虛表是一個(gè)存放虛函數(shù)地址的數(shù)組。讓我們看一下下面的程
序來(lái)理解這種思想。
程序10:
#include <iostream>
using namespace std;
?
class Class {
???????? virtual void fun() { cout << "Class::fun" << endl; }
};
?
typedef void (*Fun)(void);
?
int main() {
???????? Class objClass;
?
???????? cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
???????? cout << "Value at virtual pointer i.e. Address of virtual table "
????????????????? << (int*)*(int*)(&objClass+0) << endl;
???????? cout << "Value at first entry of virtual table "
????????????????? << (int*)*(int*)*(int*)(&objClass+0) << endl;
?
???????? cout << endl << "Executing virtual function" << endl << endl;
???????? Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);
???????? pFun();
???????? return 0;
}
這個(gè)程序有些不常用的間接類型轉(zhuǎn)換。這個(gè)程序最重要的地方是
Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);
這里 Fun 是一個(gè)typeded 函數(shù)指針。
typdef void (*Fun)(void):
我們來(lái)詳細(xì)研究這種不常用的間接轉(zhuǎn)換。
(int*)(&objClass+0)給出類第一個(gè)入口的虛函數(shù)的指針然后把他類型轉(zhuǎn)換成int*.我們使用間接操作符(也就是 *)來(lái)獲取這個(gè)地址的值然后再
次類型轉(zhuǎn)換成 int* 也就是(int*)*(int*)(&objClass+0).這將會(huì)給出虛函數(shù)地址表的入口地址.獲得這個(gè)位置的值,也就是得到類的第一個(gè)虛
函數(shù)的指針,再次使用間接操作符再類型轉(zhuǎn)換成相應(yīng)的函數(shù)指針類型。像這樣:
Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);
表示獲取虛函數(shù)地址表第一個(gè)入口的值然后類換成Fun類型后保存到pFun.
添加多一個(gè)虛函數(shù)到類里面會(huì)怎么樣。我們現(xiàn)在想要訪問(wèn)虛函數(shù)地址表里第二個(gè)成員。查看以下程序,看看虛函數(shù)地址表里的值。
程序11:
#include <iostream>
using namespace std;
class Class {
???????? virtual void f() { cout << "Class::f" << endl; }
???????? virtual void g() { cout << "Class::g" << endl; }
};
int main() {
???????? Class objClass;
???????? cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
???????? cout << "Value at virtual pointer i.e. Address of virtual table "
???????????????? << (int*)*(int*)(&objClass+0) << endl;
???????? cout << endl << "Information about VTable" << endl << endl;
???????? cout << "Value at 1st entry of VTable "
???????????????? << (int*)*((int*)*(int*)(&objClass+0)+0) << endl;
???????? cout << "Value at 2nd entry of VTable "
???????????????? << (int*)*((int*)*(int*)(&objClass+0)+1) << endl;
???????? return 0;
}
程序輸出:
Address of virtual pointer 0012FF7C
Value at virtual pointer i.e. Address of virtual table 0046C0EC
Information about VTable
Value at 1st entry of VTable 0040100A
Value at 2nd entry of VTable 0040129E
現(xiàn)在我們心理產(chǎn)生一個(gè)問(wèn)題。編譯器怎么知道虛函數(shù)地址表的長(zhǎng)度呢?答案是:虛函數(shù)地址表最后的入口等于NULL。把程序做些小改動(dòng)來(lái)理解
它。
程序12:
#include <iostream>
using namespace std;
class Class {
???????? virtual void f() { cout << "Class::f" << endl; }
???????? virtual void g() { cout << "Class::g" << endl; }
};
int main() {
???????? Class objClass;
???????? cout << "Address of virtual pointer " << (int*)(&objClass+0) << endl;
???????? cout << "Value at virtual pointer i.e. Address of virtual table "
????????????????? << (int*)*(int*)(&objClass+0) << endl;
???????? cout << endl << "Information about VTable" << endl << endl;
???????? cout << "Value at 1st entry of VTable "
????????????????? << (int*)*((int*)*(int*)(&objClass+0)+0) << endl;
???????? cout << "Value at 2nd entry of VTable "
????????????????? << (int*)*((int*)*(int*)(&objClass+0)+1) << endl;
???????? cout << "Value at 3rd entry of VTable "
????????????????? << (int*)*((int*)*(int*)(&objClass+0)+2) << endl;
???????? cout << "Value at 4th entry of VTable "
????????????????? << (int*)*((int*)*(int*)(&objClass+0)+3) << endl;
???????? return 0;
}
程序輸出:
Address of virtual pointer 0012FF7C
Value at virtual pointer i.e. Address of virtual table 0046C134
Information about VTable
Value at 1st entry of VTable 0040100A
Value at 2nd entry of VTable 0040129E
Value at 3rd entry of VTable 00000000
Value at 4th entry of VTable 73616C43
這個(gè)程序的輸出顯示了虛函數(shù)地址表的最后一個(gè)入口等于NULL.我們用我們所了解的知識(shí)來(lái)調(diào)用虛函數(shù)
程序13:
#include <iostream>
using namespace std;
class Class {
???????? virtual void f() { cout << "Class::f" << endl; }
???????? virtual void g() { cout << "Class::g" << endl; }
};
typedef void(*Fun)(void);
int main() {
???????? Class objClass;
???????? Fun pFun = NULL;
???????? // calling 1st virtual function
???????? pFun = (Fun)*((int*)*(int*)(&objClass+0)+0);
???????? pFun();
???????? // calling 2nd virtual function
???????? pFun = (Fun)*((int*)*(int*)(&objClass+0)+1);
???????? pFun();
???????? return 0;
}
這個(gè)程序的輸出是:
Class::f
Class::g
現(xiàn)在我們來(lái)看一下多層繼承的情況。以下是個(gè)簡(jiǎn)單的多層繼承的情況。
程序14:
#include <iostream>
using namespace std;
class Base1 {
public:
???????? virtual void f() { }
};
class Base2 {
public:
???????? virtual void f() { }
};
class Base3 {
public:
???????? virtual void f() { }
};
class Drive : public Base1, public Base2, public Base3 {
};
int main() {
???????? Drive objDrive;
???????? cout << "Size is = " << sizeof(objDrive) << endl;
???????? return 0;
}
程序輸出:
Size is = 12
程序表明,當(dāng)你的 drive 類具有一個(gè)以上的基類時(shí),drive 類就具有所有基類的虛函數(shù)指針。
如果 drive 類也具有虛函數(shù)那會(huì)怎么樣呢。我們來(lái)看看這個(gè)程序以便更好的了解多繼承虛函數(shù)的概念。
程序15:
#include <iostream>
using namespace std;
?
class Base1 {
???????? virtual void f() { cout << "Base1::f" << endl; }
???????? virtual void g() { cout << "Base1::g" << endl; }
};
?
class Base2 {
???????? virtual void f() { cout << "Base2::f" << endl; }
???????? virtual void g() { cout << "Base2::g" << endl; }
};
?
class Base3 {
???????? virtual void f() { cout << "Base3::f" << endl; }
???????? virtual void g() { cout << "Base3::g" << endl; }
};
?
class Drive : public Base1, public Base2, public Base3 {
public:
???????? virtual void fd() { cout << "Drive::fd" << endl; }
???????? virtual void gd() { cout << "Drive::gd" << endl; }
};
?
typedef void(*Fun)(void);
?
int main() {
???????? Drive objDrive;
?
???????? Fun pFun = NULL;
?
???????? // calling 1st virtual function of Base1
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+0);
???????? pFun();
????????
???????? // calling 2nd virtual function of Base1
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+1);
???????? pFun();
?
???????? // calling 1st virtual function of Base2
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+1)+0);
???????? pFun();
?
???????? // calling 2nd virtual function of Base2
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+1)+1);
???????? pFun();
?
???????? // calling 1st virtual function of Base3
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+2)+0);
???????? pFun();
?
???????? // calling 2nd virtual function of Base3
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+2)+1);
???????? pFun();
?
???????? // calling 1st virtual function of Drive
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+2);
???????? pFun();
?
???????? // calling 2nd virtual function of Drive
???????? pFun = (Fun)*((int*)*(int*)((int*)&objDrive+0)+3);
???????? pFun();
?
???????? return 0;
}
程序輸出:
Base1::f
Base1::g
Base2::f
Base2::f
Base3::f
Base3::f
Drive::fd
Drive::gd
這個(gè)程序顯示 drive 的虛函數(shù)保存在 vptr 的第一個(gè) 虛函數(shù)地址表.
我們?cè)?static_cast 的幫助下我門能夠獲取 Drive 類 vptr 的偏移量.我們看一下以下的程序來(lái)更好地理解他。
程序16:
#include <iostream>
using namespace std;
?
class Base1 {
public:
???????? virtual void f() { }
};
?
class Base2 {
public:
???????? virtual void f() { }
};
?
class Base3 {
public:
???????? virtual void f() { }
};
?
class Drive : public Base1, public Base2, public Base3 {
};
?
// any non zero value because multiply zero with any no is zero
#define SOME_VALUE??????? 1
?
int main() {
???????? cout << (DWORD)static_cast<Base1*>((Drive*)SOME_VALUE)-SOME_VALUE << endl;
???????? cout << (DWORD)static_cast<Base2*>((Drive*)SOME_VALUE)-SOME_VALUE << endl;
???????? cout << (DWORD)static_cast<Base3*>((Drive*)SOME_VALUE)-SOME_VALUE << endl;
???????? return 0;
}
ATL 使用一個(gè)叫做offsetofclass 的宏來(lái)這么做,該宏定義在 ATLDEF.h 里。宏的定義是:
#define offsetofclass(base, derived) /
?????? ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
這個(gè)宏返回在 drive 類對(duì)象模型里的基類的vptr的偏移量。我們來(lái)看一個(gè)例子了解這個(gè)概念。
程序17:
#include <windows.h>
#include <iostream>
using namespace std;
?
class Base1 {
public:
???????? virtual void f() { }
};
?
class Base2 {
public:
???????? virtual void f() { }
};
?
class Base3 {
public:
???????? virtual void f() { }
};
?
class Drive : public Base1, public Base2, public Base3 {
};
?
#define _ATL_PACKING 8
?
#define offsetofclass(base, derived) /
???????? ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
?
int main() {
???????? cout << offsetofclass(Base1, Drive) << endl;
???????? cout << offsetofclass(Base2, Drive) << endl;
???????? cout << offsetofclass(Base3, Drive) << endl;
???????? return 0;
}
這是 drive 類的內(nèi)存層次
程序輸出是:
0
4
8
程序的輸出顯示,這個(gè)宏返回了所要的基類vptr的偏移量。在 Don Box 的《Essential COM》,他使用一個(gè)類似的宏來(lái)這么做。把程序做些小改動(dòng)來(lái)用Box的宏取代ATL 的宏。
程序18:
#include <windows.h>
#include <iostream>
using namespace std;
?
class Base1 {
public:
???????? virtual void f() { }
};
?
class Base2 {
public:
???????? virtual void f() { }
};
?
class Base3 {
public:
???????? virtual void f() { }
};
?
class Drive : public Base1, public Base2, public Base3 {
};
?
#define BASE_OFFSET(ClassName, BaseName) /
???????? (DWORD(static_cast<BaseName*>(reinterpret_cast<ClassName*>/
???????? (0x10000000))) - 0x10000000)
?
int main() {
???????? cout << BASE_OFFSET(Drive, Base1) << endl;
???????? cout << BASE_OFFSET(Drive, Base2) << endl;
???????? cout << BASE_OFFSET(Drive, Base3) << endl;
???????? return 0;
}
程序的目的很輸出和前面的程序一樣。
我們用這個(gè)宏在我們的程序做些實(shí)用的事。事實(shí)上,我們可以通過(guò)獲取在 drive的內(nèi)存結(jié)構(gòu)里基類的vptr來(lái)調(diào)用所要的基類的虛函數(shù)。
程序19:
#include <windows.h>
#include <iostream>
using namespace std;
?
class Base1 {
public:
???????? virtual void f() { cout << "Base1::f()" << endl; }
};
?
class Base2 {
public:
???????? virtual void f() { cout << "Base2::f()" << endl; }
};
?
class Base3 {
public:
???????? virtual void f() { cout << "Base3::f()" << endl; }
};
?
class Drive : public Base1, public Base2, public Base3 {
};
?
#define _ATL_PACKING 8
?
#define offsetofclass(base, derived) /
???????? ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
?
int main() {
???????? Drive d;
?
???????? void* pVoid = NULL;
?
???????? // call function of Base1
???????? pVoid = (char*)&d + offsetofclass(Base1, Drive);
???????? ((Base1*)(pVoid))->f();
?
???????? // call function of Base2
???????? pVoid = (char*)&d + offsetofclass(Base2, Drive);
???????? ((Base2*)(pVoid))->f();
?
???????? // call function of Base1
???????? pVoid = (char*)&d + offsetofclass(Base3, Drive);
???????? ((Base3*)(pVoid))->f();
?
???????? return 0;
}
這個(gè)程序的輸出:
Base1::f()
Base2::f()
Base3::f()
在這個(gè)指南里,我設(shè)法解釋 ATL 里 offsetofclass 宏的工作原理。我希望在下一篇文章探測(cè)其他神秘的ATL。
總結(jié)
以上是生活随笔為你收集整理的ATL offsetofclass 的工作原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python处理rgb_如何在Pytho
- 下一篇: css不换行属性_那些不常见,但却非常实