ATL Under the Hood - Part 2






4.90/5 (32 votes)
This is article 2 in a series of tutorials that discuss some of the inner workings of ATL and the techniques that ATL uses.
Introduction
In this series of tutorials I am going to discuss some of the inner workings of ATL and the techniques that ATL uses. This is the second article in the series.
Let's explore some more interesting stuff behind the virtual function. To make things consistent I am going to take the same number sequence and start my discussion with Program 20.
Let's take a look at the following Program
Program 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; }The output of the program is
Drive::funThis program clearly shows how the base class's function calls the drive class function if that function is virtual. This technique is used in different frameworks like MFC and design pattern like Template Design Pattern. Now change program little bit to see its behavior. Now I m going to call virtual function from constructor of Base class rather than member function.
Program 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; }The output of this program is
Base::funThis program shows that we can't call a virtual function of a derived class from constructor of a base class. Ok to see what is going on under the hood let's print the value of this pointer in both constructors. To make things simple remove other functions from the classes.
Program 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; }The output of the program is
In Base This Pointer = 0012FF7C In Drive This Pointer = 0012FF7C In Main 0012FF7CThis shows that there is only one object in the memory location. Now let's print the value at this pointer, i.e. value of vptr and address of VTable.
Program 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; }The output of this program is
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
This program shows the different vtable address in Base class and Drive class. To get more better understanding lets make inheritance
deeper and add one more class MostDrive
inherited from Drive and make an object of it.
Program 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; }The output of this program is
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 = 00401186This program shows that virtual pointer in initialized in the constructor of each class. Therefore the address of Vtable is different in each class constructor and main use the vtable of most drive class in inheritance chain whose object is created.
Now see what each class constructor place in vtable. To do this take pointer to function and store value of first entry of vtable in that function pointer and try to execute it.
Program 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; }The output of this program is
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::f1This program shows that constructor of each class fills the vtable entries with their own virtual function. So
Base
class fills the vtable with the address of the Base
's virtual functions and when
Drive
class's constructor executes it will create another vtable and store the virtual functions address.
Now see the situation when there is more than one virtual function in the base class and drive class override not all of these.
Program 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; }The output of this program is
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 = 00000000The output of this program shows that the base class's virtual function is not overridden in drive class then drive class constructor doesn't do anything with that entry in virtual function.
Now invite pure virtual function in this game too and see the behavior of it. Take a look at the following program
Program 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; }The output of this program is little bit different in debug and release mode. Here is the output of the debug mode
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
And here is the output in the release Mode
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 = 00401380To better understand change a program little bit and try to call virtual function from function pointer.
Program 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; }Now the behavior of program is different in debug and release Mode. In Debug mode it display run time error dialog box
And when you press Ignore button then it display one more dialog
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
Here what is R6025? It is defined in CMSGS.H file which define all error messages used within the C run time library.
#define _RT_PUREVIRT_TXT "R6025" EOL "- pure virtual function call" EOL
In fact when we define pure virtual function then compiler place the address of one of the C Runtime library function
_purecall
. This function is define in PUREVIRT.C and have the following prototype.
void __cdecl _purecall(void)We can achieve the same behavior by directly calling this function from our program. Let's take a look at this very small program.
Program 29
int main() { _purecall(); return 0; }The out put of this program is same as previous one in both debug and release mode. To better understand this make the inheritance chain deeper and drive one more class from Drive and see the behavior of this.
Program 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; }The output of this program is
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
This program shows that both Base and Drive class make their own virtual table and initialized it with the same value. Now what happen if the inheritance is further deep and none of the class except the most drive overrides any pure virtual function? This happens in the case of COM programming where interfaces are class with only pure virtual function and one interface is inherited from another interface and only implementation class override the pure virtual function of interfaces. Then each base class constructor makes their own vtable and put the same value in its entry. So it means duplication of same code again and again.
The main philosophy of ATL is to make COM component as small as possible, but due to this behavior interface class's constructor have lot of unnecessary code. To solve this problem ATL introduce a macro
ATL_NO_VTABLE
define in ATLDEF.H file as
#define ATL_NO_VTABLE __declspec(novtable)
__declspec(novtable) is Microsoft C++ specific extended attribute of class. When it is used then compiler won't generate the code to initialize the vptr and vtable and reduce the generated code size.
Change our program little bit to better understand this what this attribute can do for us.
Program 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; }The output of this program is
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 = 00420E60This program shows one more result i.e
Drive
and MostDrive
class have the same value in its vptr, but Base class have different. In fact this is due to that we haven't use
__declspec(novtable)
attribute with Base class. Now change program little bit and inherit Drive class with the same attributes i.e.
__declspec(novtable)
Program 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; }Now the output of the program is
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 = 00420E50In MSDN it is written about
__declspec(novtable)
that it should be applied to pure virtual classes. Let's do one more experiment to understand meaning of this better.
Program 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; }Here the new thing we add in this program is
// try call first virtual function typedef void (*Fun)(); Fun pFun = (Fun)*((int*)*(int*)this+0); pFun();And when we run the application we face the same problem as previous i.e. try to call pure virtual function. It means the virtual table haven't initialized yet.
MostDrive
class is not an abstract class so we should remove __declspec(novtable)
from this class.
Program 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; }Now this programs work fine and output of this program is
MostDrive::f1It is not necessary to use this attribute in ATL class only; it can be used with any class whose object can not be created. In the same way it is not necessary to must use this with ATL class, this can be omitted from ATL class, but removing this from ATL class can generate more code.
Hope to explore some other mysterious of ATL in next article.