ATL 内部机制 - 第二部分






4.90/5 (32投票s)
这是 ATL 系列教程中的第二篇文章,讨论 ATL 的一些内部工作原理以及 ATL 使用的技术。
引言
在本系列教程中,我将讨论 ATL 的一些内部工作原理以及 ATL 使用的技术。这是本系列的第二篇文章。
让我们来探索一些关于虚函数的更有趣的内容。为了保持一致性,我将使用相同的编号序列,并从程序 20 开始我的讨论。
让我们来看下面的程序
程序 20
#include <iostream> using namespace std; class Base { public: virtual void fun() { cout << "Base::fun" << endl; } void show() { fun(); } }; class Drive : public Base { public: virtual void fun() { cout << "Drive::fun" << endl; } }; int main() { Drive d; d.show(); return 0; }程序输出是:
Drive::fun此程序清楚地显示了基类的函数如何在虚函数的情况下调用派生类的函数。这种技术被用于 MFC 等不同框架以及模板设计模式等设计模式中。现在稍微修改一下程序,看看它的行为。现在我将从基类的构造函数中调用虚函数,而不是成员函数。
程序 21
#include <iostream> using namespace std; class Base { public: Base() { fun(); } virtual void fun() { cout << "Base::fun" << endl; } }; class Drive : public Base { public: virtual void fun() { cout << "Drive::fun" << endl; } }; int main() { Drive d; return 0; }该程序的输出是
Base::fun此程序表明,我们无法从基类的构造函数中调用派生类的虚函数。为了了解幕后发生了什么,让我们打印两个构造函数中 this 指针的值。为了简化问题,请删除类中的其他函数。
程序 22
#include <iostream> using namespace std; class Base { public: Base() { cout << "In Base" << endl; cout << "This Pointer = " << (int*)this << endl; cout << endl; } virtual void f() { cout << "Base::f" << endl; } }; class Drive : public Base { public: Drive() { cout << "In Drive" << endl; cout << "This Pointer = " << (int*)this << endl; cout << endl; } virtual void f() { cout << "Drive::f" << endl; } }; int main() { Drive d; cout << "In Main" << endl; cout << (int*)&d << endl; return 0; }程序输出是:
In Base This Pointer = 0012FF7C In Drive This Pointer = 0012FF7C In Main 0012FF7C这表明内存中只有一个对象。现在让我们打印 this 指针的值,即 vptr 的值和 VTable 的地址。
程序 23
#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 = " << (int*)*(int*)*(int*)this << endl; cout << endl; } virtual void f1() { cout << "Base::f1" << endl; } }; 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 = " << (int*)*(int*)*(int*)this << endl; cout << endl; } virtual void f1() { cout << "Drive::f2" << endl; } }; int main() { Drive d; return 0; }该程序的输出是
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C08C
Value at Vtable = 004010F0
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C07C
Value at Vtable = 00401217
此程序显示了基类和派生类中不同的 vtable 地址。为了更好地理解,让我们让继承更深,并添加一个从 Drive 继承的类 `MostDrive`,并创建一个它的对象。程序 24
#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 = " << (int*)*(int*)*(int*)this << endl; cout << endl; } virtual void f1() { cout << "Base::f1" << endl; } }; 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 = " << (int*)*(int*)*(int*)this << endl; cout << endl; } virtual void f1() { cout << "Drive::f2" << 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 = " << (int*)*(int*)*(int*)this << endl; cout << endl; } virtual void f1() { cout << "MostDrive::f2" << endl; } }; int main() { MostDrive d; return 0; }该程序的输出是
In Base Virtual Pointer = 0012FF7C Address of Vtable = 0046C0A0 Value at Vtable = 004010F5 In Drive Virtual Pointer = 0012FF7C Address of Vtable = 0046C090 Value at Vtable = 00401221 In MostDrive Virtual Pointer = 0012FF7C Address of Vtable = 0046C080 Value at Vtable = 00401186此程序表明,虚指针在每个类的构造函数中都被初始化。因此,Vtable 的地址在每个类构造函数中都不同,并且继承链中对象被创建的类将使用其 vtable。
现在看看每个类构造函数在 vtable 中放置了什么。为此,将函数指针指向 vtable 的第一个条目,并尝试执行它。
程序 25
#include <iostream> using namespace std; typedef void(*Fun)(); 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 = " << (int*)*(int*)*(int*)this << endl; Fun pFun = (Fun)*(int*)*(int*)this; pFun(); cout << endl; } virtual void f1() { cout << "Base::f1" << endl; } }; 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 = " << (int*)*(int*)*(int*)this << endl; Fun pFun = (Fun)*(int*)*(int*)this; pFun(); cout << endl; } virtual void f1() { cout << "Drive::f1" << 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 = " << (int*)*(int*)*(int*)this << endl; Fun pFun = (Fun)*(int*)*(int*)this; pFun(); cout << endl; } virtual void f1() { cout << "MostDrive::f1" << endl; } }; int main() { MostDrive d; return 0; }该程序的输出是
In Base Virtual Pointer = 0012FF7C Address of Vtable = 0046C098 Value at Vtable = 004010F5 Base::f1 In Drive Virtual Pointer = 0012FF7C Address of Vtable = 0046C088 Value at Vtable = 00401221 Drive::f1 In MostDrive Virtual Pointer = 0012FF7C Address of Vtable = 0046C078 Value at Vtable = 00401186 MostDrive::f1此程序显示每个类的构造函数都用其自己的虚函数填充 vtable 条目。因此,`Base` 类用 `Base` 虚函数的地址填充 vtable,当 `Drive` 类的构造函数执行时,它将创建另一个 vtable 并存储虚函数的地址。
现在看看当基类和派生类中有多个虚函数,但派生类没有覆盖所有这些函数时的情况。
程序 26
#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 << "Value at Vtable 3rd entry = " << (int*)*((int*)*(int*)this+2) << endl; cout << endl; } virtual void f1() { cout << "Base::f1" << endl; } virtual void f2() { cout << "Base::f2" << endl; } }; 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 << "Value at Vtable 3rd entry = " << (int*)*((int*)*(int*)this+2) << endl; cout << endl; } virtual void f1() { cout << "Drive::f1" << endl; } }; int main() { Drive d; return 0; }该程序的输出是
In Base Virtual Pointer = 0012FF7C Address of Vtable = 0046C0E0 Value at Vtable 1st entry = 004010F0 Value at Vtable 2nd entry = 00401145 Value at Vtable 3rd entry = 00000000 In Drive Virtual Pointer = 0012FF7C Address of Vtable = 0046C0C8 Value at Vtable 1st entry = 0040121C Value at Vtable 2nd entry = 00401145 Value at Vtable 3rd entry = 00000000此程序的输出表明,如果派生类没有覆盖基类的虚函数,那么派生类构造函数不会对该虚函数条目做任何操作。
现在也让纯虚函数加入这个游戏,看看它的行为。看看下面的程序
程序 27
#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; } virtual void f1() { cout << "Drive::f1" << endl; } virtual void f2() { cout << "Drive::f2" << endl; } }; int main() { Drive d; return 0; }此程序的输出在调试模式和发布模式下略有不同。这是调试模式的输出
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0BC
Value at Vtable 1st entry = 00420CB0
Value at Vtable 2nd entry = 00420CB0
In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A4
Value at Vtable 1st entry = 00401212
Value at Vtable 2nd entry = 0040128F
这是发布模式下的输出In Base Virtual Pointer = 0012FF80 Address of Vtable = 0042115C Value at Vtable 1st entry = 0041245D Value at Vtable 2nd entry = 0041245D In Drive Virtual Pointer = 0012FF80 Address of Vtable = 00421154 Value at Vtable 1st entry = 00401310 Value at Vtable 2nd entry = 00401380为了更好地理解,稍微修改一下程序,尝试通过函数指针调用虚函数。
程序 28
#include <iostream> using namespace std; typedef void(*Fun)(); 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; // try to execute first virtual function Fun pFun = (Fun)*((int*)*(int*)this+0); pFun(); 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; } virtual void f1() { cout << "Drive::f1" << endl; } virtual void f2() { cout << "Drive::f2" << endl; } }; int main() { Drive d; return 0; }现在程序在调试模式和发布模式下的行为不同。在调试模式下,它会显示运行时错误对话框

当你按下“忽略”按钮后,它会显示另一个对话框

In Base
Virtual Pointer = 0012FF80
Address of Vtable = 0042115C
Value at Vtable 1st entry = 0041245D
Value at Vtable 2nd entry = 0041245D
runtime error R6025
- pure virtual function call
R6025 是什么?它定义在 CMSGS.H 文件中,该文件定义了 C 运行时库中使用的所有错误消息。#define _RT_PUREVIRT_TXT "R6025" EOL "- pure virtual function call" EOL
实际上,当我们定义纯虚函数时,编译器会放置一个 C 运行时库函数 `_purecall` 的地址。这个函数定义在 PUREVIRT.C 中,具有以下原型。void __cdecl _purecall(void)我们可以通过直接调用程序中的这个函数来实现相同的行为。让我们来看这个非常小的程序。
程序 29
int main() { _purecall(); return 0; }此程序的输出在调试和发布模式下与之前的程序相同。为了更好地理解这一点,让继承链更深,并从 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 编程中会发生,其中接口是只有纯虚函数的类,并且一个接口从另一个接口继承,而只有实现类覆盖了接口的纯虚函数。然后每个基类构造函数都会创建自己的 vtable,并在其条目中放入相同的值。这意味着代码的重复。再一次。ATL 的主要理念是使 COM 组件尽可能小,但由于这种行为,接口类的构造函数中存在大量不必要的代码。为了解决这个问题,ATL 引入了一个宏 `ATL_NO_VTABLE`,它定义在 ATLDEF.H 文件中,如下所示:
#define ATL_NO_VTABLE __declspec(novtable)
__declspec(novtable) 是 Microsoft C++ 特定的类扩展属性。当使用它时,编译器不会生成初始化 vptr 和 vtable 的代码,从而减小了生成代码的大小。稍微修改一下我们的程序,以便更好地理解这个属性对我们有什么作用。
程序 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` 类在其 vptr 中具有相同的值,但 Base 类具有不同的值。实际上,这是因为我们没有将 `__declspec(novtable)` 属性与 Base 类一起使用。现在稍微修改程序,并用相同的属性(即 `__declspec(novtable)`)继承 Drive 类。
程序 32
#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 __declspec(novtable) 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 = 0046C0C0 Value at Vtable 1st entry = 00420E50 Value at Vtable 2nd entry = 00420E50 In Drive Virtual Pointer = 0012FF7C Address of Vtable = 0046C0C0 Value at Vtable 1st entry = 00420E50 Value at Vtable 2nd entry = 00420E50 In MostDrive Virtual Pointer = 0012FF7C Address of Vtable = 0046C0C0 Value at Vtable 1st entry = 00420E50 Value at Vtable 2nd entry = 00420E50MSDN 中写道,`__declspec(novtable)` 应该应用于纯虚类。让我们再做一个实验来更好地理解它的含义。
程序 33
#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 __declspec(novtable) 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; // try call first virtual function typedef void (*Fun)(); Fun pFun = (Fun)*((int*)*(int*)this+0); pFun(); } virtual void f1() { cout << "MostDrive::f1" << endl; } virtual void f2() { cout << "MostDrive::f2" << endl; } }; int main() { MostDrive d; return 0; }这里我们在程序中添加的新内容是
// try call first virtual function typedef void (*Fun)(); Fun pFun = (Fun)*((int*)*(int*)this+0); pFun();当我们运行应用程序时,我们会遇到与之前相同的问题,即尝试调用纯虚函数。这意味着虚拟表尚未初始化。 `MostDrive` 类不是抽象类,所以我们应该从这个类中移除 `__declspec(novtable)`。
程序 34
#include <iostream> using namespace std; class Base { public: virtual void f1() = 0; virtual void f2() = 0; }; class __declspec(novtable) Drive : public Base { }; class MostDrive : public Drive { public: MostDrive() { // try call first virtual function typedef void (*Fun)(); Fun pFun = (Fun)*((int*)*(int*)this+0); pFun(); } virtual void f1() { cout << "MostDrive::f1" << endl; } virtual void f2() { cout << "MostDrive::f2" << endl; } }; int main() { MostDrive d; return 0; }现在这个程序工作正常,程序的输出是
MostDrive::f1不一定只在 ATL 类中使用此属性;它可以与任何无法创建对象的类一起使用。同样,不一定必须在 ATL 类中使用此属性,可以从 ATL 类中省略它,但从 ATL 类中删除它可能会生成更多代码。
希望在下一篇文章中探索 ATL 的其他神秘之处。