ATL布幔之下的秘密(2)

ATL布幔之下的秘密(2)

作者: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;
}
程序的输出为:
ase::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的秘密。

<script type="text/javascript"> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script>

This form of __declspec can be applied to any class declaration, but should only be applied to pure interface classes, that is, classes that will never be instantiated on their own. The __declspec stops the compiler from generating code to initialize the vfptr in the constructor(s) and destructor of the class. In many cases, this removes the only references to the vtable that are associated with the class and, thus, the linker will remove it. Using this form of __declspec can result in a significant reduction in code size.

If you attempt to instantiate a class marked with novtable and then access a class member, you will receive an access violation (AV).

( _weiming_ 发表于 2005-9-10 16:11:00)
 
作者举了这么例子,写了这么长,却始终不写为什么不对Base加__declspec(novtable)标记的原因。如果按照作者所说,ATL想尽量使COM小,那在Base上加__declspec(novtable)应该很有效的。
我来补充一下,Base不加__declspec(novtable)的标记,是为了让其所有子类都有一个初始的虚函数表,防止Base的子类,同样有__declspec(novtable)标记时(如:Drive),被非法调用,而出现的内存访问异常 ( 清气上升 发表于 2005-9-1 15:18:00)
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值