掀起ATL的红盖头(一)

原创 2003年04月01日 09:06:00

                                掀起ATL的红盖头(一)

简介

在这一系列的章节中我将讨论一下ATL内部工作原理和使用技巧

 

我们先来讨论一下程序在内存中的存放问题。

先看程序1:一个简单的程序,没有包括任何data member。分析一下它的内存结构如何。

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

 

如果我们在class里面加上一些data member,那么class的大小将是这些data member大小的和,同样在template里也是一样,下面看一下template class 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

 

由输出结果看出程序占用的内存大小是其本身的data member的大小加上基类的data member的大小。

 

下面思考一下如果程序里有虚函数的情况。请看下一例

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

 

程序输出结果表明如果我们在class里面加入一个虚函数,其占用的内存大小和一个int类型的变量一样,例如在Visual C++中它是占用了4个字节。在上面这个例子中class的内存结构由一个int类型的x和一个int类型的y再加上一个指向虚函数的指针构成,可是这个指针处于内存结构的开始还是末尾呢?让我们通过下面的程序证明一下

 

程序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的值,令x=100

       *(pInt+1) = 200;    // 想改变y的值,令x=200

 

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

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

 

       return 0;

}

 

注意其中几行代码:

       int* pInt = (int*)&objPoint;

       *(pInt+0) = 100;    // 想改变x的值,令x=100

       *(pInt+1) = 200;    // 想改变y的值,令y=200

 

程序输出结果如下:

X = 200

Y = 10

 

显然这不是我们预想的结果。本来我们想将200赋给x,可是却改变了y的值。由此看出放在内存开始位置的并不是成员变量m_ix,而是虚函数~CPoint(),成员m_ix和成员m_iy分别放在第二和第三的位置。现在我们将程序中的两行改一下

int* pInt = (int*)&objPoint;

*(pInt+1) = 100;    // 改变x的值,令x=100

*(pInt+2) = 200;    // 改变y的值,令y=200

于是我们得到了预期的结果,整个完整的程序如下:

#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

 

这些结果说明:无论我们在程序中将class的虚函数声明在什么位置,系统都将内存的开始位置分配给“虚指针”

 

现在新的问题产生了,存放在“虚指针”中的内容是什么。让我们看一下下面的程序,看是否能得到什么启发

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

 

这个“虚指针”的内容是指向一个虚函数表的地址,而虚函数表里存放着class中所有的虚函数的地址。让我们看一下下面的程序。

 

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

在这里Fun被定义成一个函数指针:typedef void (*Fun)(void);

让我们逐层分析:(int *)(&objClass+0)得到类的虚指针的地址并将地址转换成int指针型,为了得到虚指针的内容我们在前面加上指针*,并将其转换成int指针型,变成如下:(int *)*(int *)(&objClass+0),这时我们得到了虚函数表的入口地址,要得到地址的值我们再在前面加上指针*,然后将其转换成一个函数指针,如下:

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

EnMeans get the value from the first entry of the virtual table and store it in pFun after typecast it into the Fun type.

pFun就成为了指向虚函数的指针函数


如果class中不止一个虚函数呢?让我们来看一下怎么样得到虚函数表的第二个成员,看下面的程序。

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

      

       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

程序输出结果表示:如果drive类中有n个基类,编辑器分配内存的时候就会有n个虚指针指向各基类的虚函数表


如果drive雷本身也有虚函数,内存分配情况会是怎么样呢?让我们看下面的程序以便更好的理解有多个继承的情况。

 

程序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类的虚函数放在第一个虚指针指向的虚函数的后面。


我们也可以用static_cast来得到drive类的虚指针的相对地址,让我们看一下下面的程序来更好的理解。

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

}

 

ATLATLDEF.H头文件中用一个叫offsetofclass的宏变量来做到这一点,宏定义如下:

#define offsetofclass(base, derived) /

       ((DWORD)(static_cast<base*>((derived*)_ATL_PACKING))-_ATL_PACKING)

 

这个宏变量返回drive的基类的虚指针的相对位移,让我们看下面一个例子。

#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的内存分配如下

 

程序输出结果:

0

4

8

 

程序输出说明这个宏的返回值是其所指定的基类的虚指针的相对位置,在Don Box所写的《Essential COM》一书中,用了一个简单的宏来完成这些工作。我们把程序作一个小小的改动来展示一下Box的宏

#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;

}

 

程序的输出结果和原先的程序一样。

 

让我们来用这些宏作一些练习,实际上我们是通过取得这些虚函数的相对位移来调用这些虚函数

#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的其他特性。

掀起权证的红盖头

主持人:小张老师       特邀嘉宾:联合证券研究所 杨戈   主持人小张老师的话:日前,沪深两交易所先后发布《权证业务管理暂行办法》征求意见稿,这意味着权证这一金融品种有望在内地证券市场再度亮...
  • ruixj
  • ruixj
  • 2007年04月26日 10:02
  • 820

掀起“红盖头”:揭开真正意义的DSP神秘面纱

转载:http://www.ipinyou.com.cn/UI/technicalnews/201203/dsp_0.html 当前,你要问中国网络广告领域里最热门的话题是什么?一定会有人告...
  • zengxiaosen
  • zengxiaosen
  • 2016年10月09日 14:23
  • 423

C++ STL,ATL,WTL之间的联系和区别

STL即 Standard Template Library (标准模板库) STL是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和Davi...
  • xdrt81y
  • xdrt81y
  • 2013年12月05日 16:09
  • 11447

ATL编程初级教程(图文事例)(VC6)

http://blog.csdn.net/titilima/archive/2004/07/18/44273.aspx介绍  本教程的目的是告诉你如何使用ATL创建一个COM服务器,并使用Visual...
  • ice197983
  • ice197983
  • 2007年03月26日 11:25
  • 3005

VS2010 ATL服务程序编写全攻略(一) - 建立ATL服务

网上有很多关于编写ATL服务程序的代码和文章,但多数仍使用Visual C++ 6.0的ATL服务程序框架。对于XP系统,Visual C++ 6.0提供的框架能够正常工作,但对于Vista、Wind...
  • sonsie007
  • sonsie007
  • 2013年04月22日 15:06
  • 7396

ATL和MFC创建ActiveX控件的区别

ATL和MFC创建ActiveX控件的区别 ATL和MFC创建ActiveX控件的区别 在visual C++ 6.0中,ATL和MFC代表了两种不同的框架,分别面向不同类型的基于Window...
  • ljh081231
  • ljh081231
  • 2014年04月11日 10:18
  • 4675

ATL--创建简单的ATL之dll工程,添加“ATL简单对象”类的参数说明

添加“ATL简单对象”类一共分三个步骤 1.添加名称 2.文件类型选项 3.参数选项 添加接口 1.鼠标右键工程添加类 2.类型选择“ATL简单对象” 3.就在简...
  • sakawa_x
  • sakawa_x
  • 2017年07月21日 16:26
  • 313

MFC和ATL共享的新类CImage为图像处理提供了许多相应的处理方法

CImage类 我们知道,Visual C++的CBitmap类和静态图片控件的功能是比较弱的,它只能显示出在资源中的图标、位图、光标以及图元文件的内容,而不像VB中的Image控件可 ...
  • ghevinn
  • ghevinn
  • 2014年06月04日 11:45
  • 2387

ATL模型转换技术详解

ATL是ATLAS Transformation Language的简称,它是ATLAS研究组开发出来的一种符合OMG的一个QVT提案的模型转换语言。本文根据ATL的官方资料及个人使用经验理解并整理,...
  • tyhj_sf
  • tyhj_sf
  • 2016年07月20日 09:10
  • 1500

8.ATL实现简单的COM

本文演示了如何使用ATL开发简单的COM,同时对于ATL实现COM的原理给出了简要分析,对照博客和代码可掌握简单的ATL开发COM流程。...
  • wenzhou1219
  • wenzhou1219
  • 2016年07月16日 20:11
  • 1468
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:掀起ATL的红盖头(一)
举报原因:
原因补充:

(最多只允许输入30个字)