科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件ATL布幔下的秘密之虚函数背后的东西

ATL布幔下的秘密之虚函数背后的东西

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

在本系列的教程中,我要讨论一些ATL的内部工作方式以及它所使用的技术,这是本系列的第二篇文章。

作者:李马编译 来源:VCKBASE 2007年10月19日

关键字:

  • 评论
  • 分享微博
  • 分享邮件
那么这里的R6025是什么?它定义于CMSGS.H头文件中,这个头文件定义了所有C Run Time Library的所有错误信息。

#define _RT_PUREVIRT_TXT "R6025" EOL "- pure virtual function call" EOL

  事实上,当我们定义了纯虚函数后,编译器就会放置一个名为_purecall的C Run Time Library的函数地址。这个函数定义在 PUREVIRT.C之中,它的原型如下:

void __cdecl _purecall(void); // 译注:原文此处无分号

  我们可以在程序中直接调用这个函数来达到相同的效果,请看下面这个小程序:

  程序29.

int main() {
 _purecall();
 return 0;
}

  这个程序在debug模式和release模式下的输出和前一个是一样的。为了更好的理解这个问题,让我们把继承链弄得更深一些,并且从Drive类中再继承一个类来看看效果吧。

  程序30.

#include <iostream>
using namespace std;

class Base {
 public:
 Base() {
  cout << "In Base" << endl;
  cout << "Virtual Pointer = " << (int*)this << endl;
  cout << "Address of Vtable = " << (int*)*(int*)this << endl;
  cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
  cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
  cout << endl;
 }
 virtual void f1() = 0;
 virtual void f2() = 0;
};

class Drive : public Base {
 public:
 Drive() {
  cout << "In Drive" << endl;
  cout << "Virtual Pointer = " << (int*)this << endl;
  cout << "Address of Vtable = " << (int*)*(int*)this << endl;
  cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
  cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
  cout << endl;
 }
};

class MostDrive : public Drive {
 public:
 MostDrive() {
  cout << "In MostDrive" << endl;
  cout << "Virtual Pointer = " << (int*)this << endl;
  cout << "Address of Vtable = " << (int*)*(int*)this << endl;
  cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
  cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
  cout << endl;
 }
 virtual void f1() { cout << "MostDrive::f1" << endl; }
 virtual void f2() { cout << "MostDrive::f2" << endl; }
};

int main() {
 MostDrive d;
 return 0;
}

  程序的输出为:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0D8
Value at Vtable 1st entry = 00420F40
Value at Vtable 2nd entry = 00420F40

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420F40
Value at Vtable 2nd entry = 00420F40

In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A8
Value at Vtable 1st entry = 00401186
Value at Vtable 2nd entry = 004010F5

  这个程序表明,Base和Drive类是用相同的值来初始化各自的虚函数表的。那么,如果继承更深一些,并且只有最底层的派生类重写了纯虚函数,在这种情况下又会发生什么呢?这就是在COM程序设计的情况下所发生的了——接口就是只拥有纯虚函数的类,并且一个接口是继承自另一个接口的,只有实现类才会重写接口的纯虚函数。这样一来,每个基类的构造函数就会以相同的值来初始化它们自己的虚函数表入口。所以,这就意味着相同的代码会反复重复下去。

  ATL的主要思想就是让COM组件尽可能的小,但是由于这一行为,接口类的构造函数就会拥有很多不必要的代码。为了解决这一问题,ATL引入了一个宏ATL_NO_VTABLE,它定义在ATLDEF.H中:

#define ATL_NO_VTABLE __declspec(novtable)

  __declspec(novtable)为Microsoft C++扩展的类属性。它会使编译器不产生初始化虚函数表指针和虚函数表的代码,这样一来就减少了生成代码的尺寸。

  那么,我们来修改一下我们的代码,这样就能更好的明白这一属性究竟能为我们做什么了。

  程序31.

#include <iostream>
using namespace std;

class Base {
 public:
 Base() {
  cout << "In Base" << endl;
  cout << "Virtual Pointer = " << (int*)this << endl;
  cout << "Address of Vtable = " << (int*)*(int*)this << endl;
  cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
  cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
  cout << endl;
 }
 virtual void f1() = 0;
 virtual void f2() = 0;
};

class Drive : public Base {
 public:
 Drive() {
  cout << "In Drive" << endl;
  cout << "Virtual Pointer = " << (int*)this << endl;
  cout << "Address of Vtable = " << (int*)*(int*)this << endl;
  cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
  cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
  cout << endl;
 }
};

class __declspec(novtable) MostDrive : public Drive {
 public:
 MostDrive() {
  cout << "In MostDrive" << endl;
  cout << "Virtual Pointer = " << (int*)this << endl;
  cout << "Address of Vtable = " << (int*)*(int*)this << endl;
  cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
  cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
  cout << endl;
 }
 virtual void f1() { cout << "MostDrive::f1" << endl; }
 virtual void f2() { cout << "MostDrive::f2" << endl; }
};

int main() {
 MostDrive d;
 return 0;
}

  程序的输出为:

In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0CC
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0B4
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60

In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0B4
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60

  这个程序有另外一个结果,也就是Drive和MostDrive类拥有相同的虚函数表地址,但是Base类却不同。事实上,这就是由于我们没有对Base类使用__declspec(novtable)属性的缘故。现在,我们来对继承的Drive类也使用相同的属性,也就是__declspec(novtable)。
    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章