ATL布幔之下的秘密(2)

转载 2012年03月28日 18:28:37

作者:Zeeshan Amjad
译者:李马 (home.nuc.edu.cn/~titilima)
原文出处: http://www.codeproject.com/atl/atl_underthehood_2.asp

介绍
   在本系列的教程中,我要讨论一些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和设计模式(比如Template Design Pattern)。现在你可以修改一下这个程序来看看它的行为,我将要在基类的构造函数中调用虚函数,而不是普通的成员函数。

程序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
这个程序表明,我们不能在基类的构造函数中调用派生类的虚函数。好了,那就让我们来看看着布幔之下到底做了什么。我将会把这些构造函数之中的指针值打印出来,为了简便起见,我移除了类中其它的函数。
程序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
这就表示,整个内存位置中,只有一个对象的存在。那么就让我们把这个指针指向的值打印出来,也就是虚函数表的指针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
这个程序示范了基类和派生类中不同的虚函数表地址。为了更好地弄懂这一问题,那就让我们把继承层次加深,并添加一个继承于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
这个程序示范了虚函数表指针在每个类的构造函数中的初始化过程。这样看来,每个类构造函数中虚函数表的地址是不同的,main函数则使用了继承链中的最底部的派生类来创建对象。
   现在你可以看看虚函数表中各个类的构造函数的位置了,你可以将虚函数表中的第一个入口存入一个函数指针,并尝试运行它。
程序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
这个程序示范了每个类中的构造函数是如何用自己的虚函数来填充虚函数表中的各入口的。所以,Base类使用Base类的虚函数地址来填充自己的虚函数表,当Drive类的构造函数执行它的时候会创建另外一个虚函数表,并存储自己的虚函数地址。
   现在,你会发现在基类中含有多个虚函数的情况下,派生类并不能完全重写它们。
程序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;
}

在debug和release模式下,程序的输出有所不同。下面是debug模式的输出:
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
以下则是release模式的输出:
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;
        
        // 尝试执行第一个虚函数
        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;
}

现在程序的行为在debug和release模式下仍然不同。在debug模式下,它会显示一个运行时错误的对话框:

并且,当你按下“忽略”按钮之后,它会显示下面的对话框:

而在release模式下运行的话,它只会在控制台窗口中输出错误信息:
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 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)。
程序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 = 00420E50
在MSDN中,对__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;

        // 尝试调用第一个虚函数
        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;
}


我们在程序中添加的新东西是:
// 尝试调用第一个虚函数
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() {

        // 尝试调用第一个虚函数
        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布幔之下的秘密(1)

作者:Zeeshan Amjad 译者:李马 原文出处: http://www.codeproject.com/atl/atl_underthehood_.asp 介绍    在本系...

ATL布幔之下的秘密(1)

作者:Zeeshan Amjad 译者:李马 (home.nuc.edu.cn/~titilima ) 原文出处: http://www.codeproject.com/atl/atl_u...
  • tzleo
  • tzleo
  • 2011年04月11日 09:46
  • 531

ATL布幔下的秘密之内部工作方式

介绍   在本系列的教程中,我要讨论一些ATL的内部工作方式以及它所使用的技术。   在讨论的开始,让我们先看看一个程序的内存分布。首先,编写一个简单的程序,它没有任何的数据成员,你可...
  • ilvu999
  • ilvu999
  • 2011年12月22日 15:23
  • 191

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

介绍   在本系列的教程中,我要讨论一些ATL的内部工作方式以及它所使用的技术,这是本系列的第二篇文章。   现在让我们来探究一些虚函数背后更加有趣的资料。为了和上文保持一致,在本文的...
  • ilvu999
  • ilvu999
  • 2011年12月22日 15:10
  • 207

ATL布幔下的秘密之底层技术和汇编

介绍   到现在为止,我们还没有讨论过任何有关汇编语言的东西。但是如果我们真的要了解ATL底层内幕的话,就不能回避这一话题,因为ATL使用了一些底层的技术以及一些内联汇编语言来使它更小巧快速。在...
  • ilvu999
  • ilvu999
  • 2011年12月22日 15:05
  • 195

ATL布幔下的秘密之窗口类的秘密

介绍   很多人认为ATL只是用来编写COM组件的,其实你也可以使用ATL中的窗口类来创建基于窗口的应用程序。虽然你可以将基于MFC的程序转换为ATL,但是ATL中对于UI(译注:用户界面)组...
  • ilvu999
  • ilvu999
  • 2011年12月22日 14:52
  • 244

万众瞩目之下,ANGULAR 2终于正式发布啦!

转载:https://angular.io/怀着期盼的心情,终于盼到了稳定版本,那么我就可以专心研究了,不再为不定期的修复烦恼咯。今天,在 Google 总部一个特别的聚会上,我们发布了 Angula...

ACM-DFS之下沙小面的(2)——hdu1572

ACM 搜索 DFS 下沙小面的(2) hdu1572

ATL Internals 2ed复习.chapter 7.COM Persistence

需要保存自身状态的object应该提供IPersistMedium,这里Medium=Stream,Storage,PropertyBag等 client决定何时在何种媒体上要保存object的状态...
  • mcmcmc
  • mcmcmc
  • 2012年10月07日 17:26
  • 566

ATL Internals 2ed复习.chapter 3.CAutoPtr

ATL提供了CAutoPtr, CAutoVectorPtr, CAutoPtrArray, and CAutoPtrList,它们具有类似auto_ptr功能,而没有抛出异常,并且不需要借助于CRT...
  • mcmcmc
  • mcmcmc
  • 2012年10月06日 17:30
  • 437
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ATL布幔之下的秘密(2)
举报原因:
原因补充:

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