日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ATL offsetofclass 的工作原理

發(fā)布時間:2025/3/15 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ATL offsetofclass 的工作原理 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

?ATL深入淺出[1]

作者:ErIM Creator
日期:2007-7-20
參考:http://www.codeproject.com/atl/atl_underthehood_.asp

在這一系列的文章里,我將和大家一起討論ATL的底層工作和ATL所使用到的技術(shù)。如果你只想嘗試寫個普通的ATL控件,這篇文章對你一點幫助

都沒有;如果你想更好的學(xué)會使用ATL,認真往下看把。

我們先來討論一個類(Class)的存儲配置。首先來寫一個類,這個類沒有任何數(shù)據(jù)成員,然后來看看他的內(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ù)成員,他的大小會將等于所有數(shù)據(jù)成員所占內(nèi)存的和。在模版類(template class)中,也是這樣。現(xiàn)在,讓我

們來看看模版類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)在,我們也添加一個繼承類到這個程序,我們用Point3D類來繼承Point類,同時來看看程序的內(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

這個程序說明了派生類的內(nèi)存結(jié)構(gòu)。派生類所占用的內(nèi)存總數(shù)是基類的所有數(shù)據(jù)成員和該派生類所有數(shù)據(jù)成員所占內(nèi)存的和。


如果把虛函數(shù)也添加進來,那這個問題就更有趣了。我們來看看這個程序。
程序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

如果我們添加一個以上的需函數(shù),那么情況會變得更有去。

程序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;
}
程序的輸出和上面一樣。我們來做多點實驗,使我們能更好地理解他。

程序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

這個程序的輸出告訴我們,無論你在類里添加多少個虛函數(shù),他的大小只增加一個int數(shù)據(jù)類型所占的內(nèi)存空間,例如在Visual C++ 他增加4字

節(jié)。他表示有三塊內(nèi)存給這個類的整數(shù),一個給m_x,一個給m_y,還有一個用來處理被調(diào)用的虛函數(shù)的虛指針。首先來看一看新的一塊內(nèi)存,也

就是虛函數(shù)指針?biāo)純?nèi)存,在該對象的最開始頭(或者最后)。我們可以通過直接訪問對象所占的內(nèi)存塊來調(diào)用虛函數(shù)。只要把對象的地址保

存到一個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;
}
這個程序最重要的地方是:
int* pInt = (int*)&objPoint;
*(pInt+0) = 100; // 打算改變 x 的值
*(pInt+1) = 200; // 打算改變 y 的值

這里我們把對象的地址保存到一個int類型的指針,然后把它當(dāng)成整型指針。
程序輸出:
X = 200
Y = 10
當(dāng)然,這不是我們想要的結(jié)果!程序說明,這時的200是保存到x_ix,而不是x_iy。也就是說對象第一個數(shù)據(jù)成員是從內(nèi)存的第二個地址開始的

,而不是第一個。換句話說,第一個內(nèi)存地址保存的是虛函數(shù)的地址,然后其他保存的都是類的數(shù)據(jù)成員。我們來改一下下面兩行代碼。
int* pInt = (int*)&objPoint;
*(pInt+1) = 100; // 打算改變 x 的值
*(pInt+2) = 200; // 打算改變 y 的值

這時我們得到了所期待的結(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

這里很明確的告訴我們,無論什么時候我們添加虛函數(shù)到類里面,虛指針都存放在內(nèi)存的第一個位置。

現(xiàn)在問題出現(xiàn)了:虛指針里存放的是什么?來看看以下程序,我們就能知道它是什么概念。
程序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

虛指針保存一張叫做虛表的地址。而虛表保存著整個類的虛函數(shù)地址。換句話說,虛表是一個存放虛函數(shù)地址的數(shù)組。讓我們看一下下面的程

序來理解這種思想。

程序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;

}
這個程序有些不常用的間接類型轉(zhuǎn)換。這個程序最重要的地方是

Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);

這里 Fun 是一個typeded 函數(shù)指針。

typdef void (*Fun)(void):

我們來詳細研究這種不常用的間接轉(zhuǎn)換。
(int*)(&objClass+0)給出類第一個入口的虛函數(shù)的指針然后把他類型轉(zhuǎn)換成int*.我們使用間接操作符(也就是 *)來獲取這個地址的值然后再

次類型轉(zhuǎn)換成 int* 也就是(int*)*(int*)(&objClass+0).這將會給出虛函數(shù)地址表的入口地址.獲得這個位置的值,也就是得到類的第一個虛

函數(shù)的指針,再次使用間接操作符再類型轉(zhuǎn)換成相應(yīng)的函數(shù)指針類型。像這樣:
Fun pFun = (Fun)*(int*)*(int*)(&objClass+0);
表示獲取虛函數(shù)地址表第一個入口的值然后類換成Fun類型后保存到pFun.

添加多一個虛函數(shù)到類里面會怎么樣。我們現(xiàn)在想要訪問虛函數(shù)地址表里第二個成員。查看以下程序,看看虛函數(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)生一個問題。編譯器怎么知道虛函數(shù)地址表的長度呢?答案是:虛函數(shù)地址表最后的入口等于NULL。把程序做些小改動來理解

它。

程序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

這個程序的輸出顯示了虛函數(shù)地址表的最后一個入口等于NULL.我們用我們所了解的知識來調(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;
}

這個程序的輸出是:

Class::f
Class::g

現(xià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 類具有一個以上的基類時,drive 類就具有所有基類的虛函數(shù)指針。
如果 drive 類也具有虛函數(shù)那會怎么樣呢。我們來看看這個程序以便更好的了解多繼承虛函數(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

這個程序顯示 drive 的虛函數(shù)保存在 vptr 的第一個 虛函數(shù)地址表.
我們在 static_cast 的幫助下我門能夠獲取 Drive 類 vptr 的偏移量.我們看一下以下的程序來更好地理解他。

程序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 使用一個叫做offsetofclass 的宏來這么做,該宏定義在 ATLDEF.h 里。宏的定義是:
#define offsetofclass(base, derived) /
?????? ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)
這個宏返回在 drive 類對象模型里的基類的vptr的偏移量。我們來看一個例子了解這個概念。
程序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
程序的輸出顯示,這個宏返回了所要的基類vptr的偏移量。在 Don Box 的《Essential COM》,他使用一個類似的宏來這么做。把程序做些小改動來用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;

}

程序的目的很輸出和前面的程序一樣。
我們用這個宏在我們的程序做些實用的事。事實上,我們可以通過獲取在 drive的內(nèi)存結(jié)構(gòu)里基類的vptr來調(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;

}

這個程序的輸出:

Base1::f()

Base2::f()

Base3::f()

在這個指南里,我設(shè)法解釋 ATL 里 offsetofclass 宏的工作原理。我希望在下一篇文章探測其他神秘的ATL。

總結(jié)

以上是生活随笔為你收集整理的ATL offsetofclass 的工作原理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。