ATL Under the Hood - Part 1(翻译)

ATL Under the Hood - Part 1(翻译)

作者:Zeeshan Amjad

译者:姜江

QQ:457283

E-mail:jzsnmail@163.net

原文地址:http://www.codeproject.com/atl/atl_underthehood_.asp

 

介绍

       在这一系列的教程中我准备讨论一些关于ATL内部工作原理和ATL使用技术。

       让我们以一个程序的内存布局开始我们的讨论。让我们写一个简单的程序,它没有包含任何数据成员,考虑一下它的内存结构。

 

程序1

#include <iostream>

using namespace std;

 

class Class {

};

 

int main() {

       Class objClass;

       cout << "Size of object is = " << sizeof(objClass) << endl;

       cout << "Address of object is = " << &objClass << endl;

       return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 


程序的输出是:

Size of object is = 1

Address of object is = 0012FF7C

 

 

 

 


现在如果我们准备加入一些数据成员,那么类的大小是所有单独存储的成员变量的存储总和。在模版类中也是这样。现在让我们看一下模版类Point

 

程序2

#include <iostream>

using namespace std;

 

template <typename T>

class CPoint {

public:

       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

 

 

 

 


现在我们在程序中加入继承。我们准备从Point类派生出Point3D,然后观察程序结构。

程序3

#include <iostream>

using namespace std;

 

template <typename T>

class CPoint {

public:

       T m_x;

       T m_y;

};

 

template <typename T>

class CPoint3D : public CPoint<T> {

public:

       T m_z;

};

 

int main() {

       CPoint<int> objPoint;

       cout << "Size of object Point is = " << sizeof(objPoint) << endl;

       cout << "Address of object Point is = " << &objPoint << endl;

 

       CPoint3D<int> objPoint3D;

       cout << "Size of object Point3D is = " << sizeof(objPoint3D) << endl;

       cout << "Address of object Point3D is = " << &objPoint3D << endl;

 

       return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 




程序输出为:

Size of object Point is = 8

Address of object Point is = 0012FF78

Size of object Point3D is = 12

Address of object Point3D is = 0012FF6C

 

 

 

 

 

 


这个程序展示了派生类的内存结构。这个对象占用的内存大小是它的数据成员加上它的基类成员的总和。

当一个虚函数加入这个程序中事情变得有趣了,让我们看看下面这个程序。

程序4

#include <iostream>

using namespace std;

 

class Class {

public:

       virtual void fun() { cout << "Class::fun" << endl; }

};

 

int main() {

       Class objClass;

       cout << "Size of Class = " << sizeof(objClass) << endl;

       cout << "Address of Class = " << &objClass << endl;

       return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 




程序的输出结果是:

Size of Class = 4

Address of Class = 0012FF7C

 

 

 

 


当我们加入一个以上的虚函数的时候事情变得更加有趣了。

程序5

#include <iostream>

using namespace std;

 

class Class {

public:

       virtual void fun1() { cout << "Class::fun1" << endl; }

       virtual void fun2() { cout << "Class::fun2" << endl; }

       virtual void fun3() { cout << "Class::fun3" << endl; }

};

 

int main() {

       Class objClass;

       cout << "Size of Class = " << sizeof(objClass) << endl;

       cout << "Address of Class = " << &objClass << endl;

       return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


程序的输出的结果跟上面哪个程序一样。让我们多做一些试验来理解它。

程序6

 

#include <iostream>

using namespace std;

 

class CPoint {

public:

       int m_ix;

       int m_iy;

       virtual ~CPoint() { };

};

 

int main() {

       CPoint objPoint;

       cout << "Size of Class = " << sizeof(objPoint) << endl;

       cout << "Address of Class = " << &objPoint << endl;

       return 0;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 




程序的输出结果是:

Size of Class = 12

Address of Class = 0012FF68

 

 

 

 


这些程序的输出结果说明了当你在类中加入任何虚函数,该类增加了一个int类型的大小。也就是在Virtual C++中它增加4个字节。也就是说在这个类中有3个整型大小的位置,一个给x、一个给y还有一个给处理虚函数的指针,它被叫做虚函数指针(Virtual Pointer)。首先观察一下这个新的位置,也就是虚函数指针,它在对象的开始或者结尾。我们准备直接访问被这个对象占用的内存来看看这个虚函数指针。通过用一个巧妙的指针算法来做这个实验。


程序
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;    // want to change the value of x

       *(pInt+1) = 200;    // want to change the value of y

 

       cout << "X = " << objPoint.getX() << endl;

       cout << "Y = " << objPoint.getY() << endl;

 

       return 0;

}

 

 

 


 



























 

 

 

 


 

这个程序最重要的东西:

int* pInt = (int*)&objPoint;

*(pInt+0) = 100;    // want to change the value of x

*(pInt+1) = 200;    // want to change the value of y

 

 

 

 

 



X = 200

Y = 10

我们将对象当转换成整型指针,然后将它的地址存储在一个整型指针内。程序的输出结果为:

 

 

 

 

当然,这不是我们需要的结果。这个说明200存储在m_ix数据成员驻留的位置。也就是说明第一个成员变量m_ix开始于第二个内存位置而不是第一个。换句话说就是第一个成员是虚函数指针(Virtual Pointer),然后剩下的是对象的数据成员。改变下面两行程序:

int* pInt = (int*)&objPoint;

*(pInt+1) = 100;    // want to change the value of x

*(pInt+2) = 200;    // want to change the value of y

 

 

 

 

 


我们得到了所期望的结果,下面是完整的程序。

程序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;    // want to change the value of x

       *(pInt+2) = 200;    // want to change the value of y

 

       cout << "X = " << objPoint.getX() << endl;

       cout << "Y = " << objPoint.getY() << endl;

 

       return 0;

}

 

 

 


 


























 

 

 

 


程序的输出结果是:

X = 100

Y = 200

 

 

 


 

这清楚的说明了无论我们什么时候在类里加入虚函数,虚函数指针(Virtual Pointer)都是加在内存结构的第一个位置上。


 



现在问题出现了:虚函数指针里存储的是什么?看看下面的程序来理解它。

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

 

 

 

 


虚函数指针存储了被叫做虚函数表(Virtual Table)的地址。虚函数表存储了类中所有的虚函数的地址。换句话说虚函数表是一个虚函数地址的数组。让我们观察下面的程序来理解它。

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

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 






这个程序有许多少见的间接类型转换。这个程序中最重要的一行是:

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

 

 

 


Funtypedef定义的函数指针

typedef void (*Fun)(void);

 

 

 


让我们研究一下这个冗长的间接类型转换。(int*)(&objClass+0) 得到该类的虚函数指针地址,它是该类的第一个进入点。我们把它转换成int*。为了获得在这个地址中的值,我们用了间接操作也就是*(解引用操作符),然后再次的将它转换成int*也就是(int*)*(int*)(&objClass+0)。这样得到虚函数表的第一个进入点的地址。为了获得这个位置内保存的值,也就是说获得该类第一个虚函数的地址,我们再一次使用间接操作*(解引用操作符),然后转换成适当的函数指针类型,因此:

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

 

 


意思是说从虚函数表的第一个进入点获得值,然后在转换成Fun类型后存储到pFun里。





当一个以上的虚函数加入到这个类中的时候会发生什么?现在我们想访问虚函数表的第二个成员。注意下面的程序,观察虚函数表的值。

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

 

 

 

 

 

 

 

 

 






现在一个问题很自然的在大脑中产生。编译器是如何知道虚函数表的长度?答案是表中最后一个进入点是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

 

 

 


 












这个程序的输出结果说明表中最后一个进入点是NULL。让我们调用虚函数。







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

 

 

 

 


现在让我们看看多继承的情况。让我们看这个简单的多继承的情况。

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

 

 

 


这个程序说明当你从多个基类派生,这个派生类有所有基类的虚函数指针。





当派生类也有序函数的时候会发生什么呢?让我们看看这个程序,更好的理解多继承的虚函数概念。

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

 

 

 

 

 

 

 

 

 

 




这个程序说明继承的虚函数在第一个虚函数指针所指向的虚函数表中。




通过static_cast我们可以获得派生类的偏移量。让我们观察下面的程序,更好的理解它。

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

 

 

 

 


该宏返回派生类对象模块中的基类虚函数指针(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;

}




 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 











派生类的内存布局是:



这个程序的输出结果是:

0

4

8

 

 

 

 

 


这个程序的输出结果说明这个宏返回基类虚函数指针(vptr)的偏移量。在Don Box的COM本质论(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;

}

 

 


 

















 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

程序输出结果和用途跟前面的那个程序是一样的。

 

让我们在程序中使用这个宏来做一些实际的事情。事实上我们可以通过在派生类内存结构中获得的基函数虚函数指针(vptr)来调用特定的基类虚涵数。

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

 

 

 

 

 


在这篇教程中我解释了ATL中Offsetofclass宏的工作原理。在下一篇文章中我想探索ATL中其他神秘的东西。
翻译过程中难免出现错误,欢迎批评指正!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值