ATL Under the Hood - Part 1 (translation)
Author: Zeeshan Amjad
Translator: Jiang Jiang
QQ: 457283
E-mail: jzsnmail@163.net
Original address: http://www.codeproject.com/atl/atl_underthehood_.asp
Introduction
In this series of tutorials I am going to discuss some of the working principles and ATL use technology on ATL.
Let us start our discussion with a program's memory layout. Let us write a simple program that does not contain any data members, consider its memory structure.
Program 1
#include
Using namespace std;
Class class {
}
Int main () {
Class Objclass;
COUT << "SIZE OF Object IS =" << sizeof (objclass) << ENDL;
COUT << "Address of object is =" << & objclass << Endl;
Return 0;
}
The output of the program is:
SIZE OF Object IS = 1
Address of Object IS = 0012FF7C
Now if we prepare to join some data members, the size of the class is the stored sum of all separately stored member variables. This is also the same in the template class. Let us now look at the template category.
Program 2
#include
Using namespace std;
Template
Class cpoint {
PUBLIC:
T m_x;
T m_y;
}
Int main () {
Cpoint
COUT << "SIZE OF Object IS =" << sizeof (objPoint) << ENDL;
Cout << "Address of object is =" << & objPoint << Endl;
Return 0;
}
The program output is now:
Size of Object IS = 8
Address of Object IS = 0012FF78
Now we join in the program. We are ready to give Point3D from the Point class, then observe the program structure.
Program 3
#include
Using namespace std;
Template
Class cpoint {
PUBLIC:
T m_x;
T m_y;
}
Template
Class cpoint3d: public cpoint
PUBLIC:
T m_z;
}
Int main () {
Cpoint
COUT << "SIZE OF Object Point IS =" << sizeof (objPoint) << ENDL;
COUT << "Address of object point is =" << & objPoint << Endl;
CPOINT3D
COUT << "Size Object Point3D IS =" << SizeOf (ObjPoint3D) << Endl; Cout << "Address of Object Point3D IS =" << & objPoint3D << Endl;
Return 0;
}
The program output is:
Size of Object Point IS = 8
Address of Object Point IS = 0012FF78
Size of Object Point3D IS = 12
Address of Object Point3D IS = 0012FF6C
This program shows the memory structure of the derived class. The memory size occupied by this object is its data member plus the sum of its base class members.
When a virtual function joins this program, let's take a look at the program below.
Program 4
#include
Using namespace std;
Class class {
PUBLIC:
Virtual void fun () {cout << "class :: fun" << endl;}
}
Int main () {
Class Objclass;
Cout << "SIZE OF Class =" << sizeof (objclass) << ENDL;
Cout << "Address of class =" << & objclass << ENDL;
Return 0;
}
The output result of the program is:
SIZE OF Class = 4
Address of class = 0012FF7C
When we join more than one virtual function, things become more interesting.
Program 5
#include
Using namespace std;
Class class {
PUBLIC:
Virtual void fun1 () {cout << "class :: fun1" << endl;}
Virtual void fun2 () {cout << "class :: fun2" << endl;}
Virtual void fun3 () {cout << "class :: fun3" << endl;}
}
Int main () {
Class Objclass;
Cout << "SIZE OF Class =" << sizeof (objclass) << ENDL;
Cout << "Address of class =" << & objclass << ENDL;
Return 0;
}
The result of the output of the program is the same as that of the above. Let us do more tests to understand it.
Program 6
#include
Using namespace std;
Class cpoint {
PUBLIC:
INT M_IX;
INT m_iy;
Virtual ~ cpoint () {};
}
Int main () {
Cpoint objPoint;
COUT << "SIZE OF Class =" << sizeof (objPoint) << Endl; cout << "address of class =" << & objpoint << endl;
Return 0;
}
The output result of the program is:
SIZE OF Class = 12
Address of class = 0012FF68
The output results of these programs illustrate when you add any virtual functions in the class, which adds an int type size. That is, it adds 4 bytes in Virtual C . That is to say, there is 3 integer sizes in this class, one gives x, one gives Y, there is a pointer to the processing virtual function, which is called virtual power. First, this new location is observed, that is, the virtual function pointer, which is at the beginning of the object. We are ready to access this virtual function pointer directly to the memory occupied by this object. Do this experiment by using a smart pointer algorithm.
Program 7
#include
Using namespace std;
Class cpoint {
PUBLIC:
INT M_IX;
INT m_iy;
CPOINT (const INT p_ix = 0, const INT p_iy = 0):
m_ix (p_ix), m_iy (p_iy) {
}
INT getx () const {
Return M_ix;
}
INT gety () const {
Return m_iy;
}
Virtual ~ cpoint () {};
}
Int main () {
Cpoint Objpoint (5, 10);
INT * PINT = (INT *) & objpoint;
* (Pint 0) = 100; // Want to Change The Value of X
* (PINT 1) = 200; // Want to Change The Value of Y
Cout << "x =" << objPoint.getx () << endl;
Cout << "y =" << objPoint.gety () << endl;
Return 0;
}
The most important thing this program:
INT * PINT = (INT *) & objpoint;
* (Pint 0) = 100; // Want to Change The Value of X
* (PINT 1) = 200; // Want to Change The Value of Y
X = 200
Y = 10
We convert objects into integer pointers and then store its address in a whole pointer. The output result of the program is:
Of course, this is not the result we need. This description 200 is stored in the location where the M_IX data member resides. That is to say, the first member variable m_ix begins in the second memory location instead of the first. In other words, the first member is a virtual power pointer, and then remains the object's data member. Change the following two lines:
INT * PINT = (INT *) & objpoint;
* (PINT 1) = 100; // Want to Change The Value of X
* (PINT 2) = 200; // Want to change the value of y We got the desired result, the following is a complete program.
Program 8
#include
Using namespace std;
Class cpoint {
PUBLIC:
INT M_IX;
INT m_iy;
CPOINT (const INT p_ix = 0, const INT p_iy = 0):
m_ix (p_ix), m_iy (p_iy) {
}
INT getx () const {
Return M_ix;
}
INT gety () const {
Return m_iy;
}
Virtual ~ cpoint () {};
}
Int main () {
Cpoint Objpoint (5, 10);
INT * PINT = (INT *) & objpoint;
* (PINT 1) = 100; // Want to Change The Value of X
* (PINT 2) = 200; // Want to Change The Value of Y
Cout << "x =" << objPoint.getx () << endl;
Cout << "y =" << objPoint.gety () << endl;
Return 0;
}
The output result of the program is:
X = 100
Y = 200
This clearly shows that no matter when we add a virtual function in the class, the virtual function pointer (Virtual Pointer) is added to the first location of the memory structure.
The problem now appears: What is stored in the virtual function pointer? Take a look at the procedure below to understand it.
Program 9
#include
Using namespace std;
Class class {
Virtual void fun () {cout << "class :: fun" << endl;}
}
Int main () {
Class Objclass;
COUT << "Address of Virtual Pointer << (int *) (& objclass 0) << endl;
Cout << "Value At Virtual Pointer" << (int *) * (INT *) (& objclass 0) << ENDL;
Return 0;
}
The result of the program output is:
Address of Virtual Pointer 0012FF7C
Value at Virtual Pointer 0046C060
The virtual function pointer stores the address called virtual function table (Virtual Table). The virtual function table stores the address of all virtual functions in the class. In other words, the virtual function table is an array of virtual function addresses. Let us observe the following procedures to understand it.
Program 10
#include
Using namespace std;
Class class {
Virtual void fun () {cout << "class :: fun" << endl;}
}
TypeDef void (* fun) (VOID);
Int main () {
Class Objclass;
COUT << "Address of Virtual Pointer << (INT *) (& Objclass 0) << endl; cout <<" Value At Virtual Pointer I.E. Address Of Virtual Table
<< (int *) * (INT *) (& objclass 0) << endl;
Cout << "Value at first entry of virtual table"
<< (int *) * (int *) * (INT *) (& objclass 0) << ENDL;
COUT << Endl << "Executing Virtual Function" << Endl << Endl;
Fun PFUN = (fun) * (int *) * (INT *) (& objclass 0);
PFUN ();
Return 0;
}
This program has many rare indirect type conversions. The most important line in this program is:
Fun PFUN = (fun) * (int *) * (INT *) (& objclass 0);
Fun is a function pointer defined by typedef
TypeDef void (* fun) (VOID);
Let us study this lengthy indirect type conversion. (INT *) (& ObjClass 0) Get the virtual function pointer address of this class, which is the first entry point of this class. We convert it into int *. In order to obtain a value in this address, we use indirect operations to * (declined the operator), and then convert it again into int * is (int *) * (INT *) (& objclass 0). This gives the first entry point of the virtual function table. In order to get the value saved in this location, it is to obtain the address of the first virtual function, we use the indirect operation * (declare), then convert to the appropriate function pointer type, so:
Fun PFUN = (fun) * (int *) * (INT *) (& objclass 0);
It means that the first entry point from the virtual function table is obtained, and then stored in the PFUn after converting into a FUN type.
What happens when more than one virtual function adds to this class? Now we want to visit the second member of the virtual function table. Note the following procedure to observe the value of the virtual function table.
Program 11
#include
Using namespace std;
Class class {
Virtual void f () {cout << "Class :: f" << endl;
Virtual void g () {cout << "class :: g" << endl;
}
Int main () {
Class Objclass;
COUT << "Address of Virtual Pointer << (int *) (& objclass 0) << endl;
Cout << "Value At Virtual Pointer I. Address of Virtual Table"
<< (int *) * (INT *) (& objclass 0) << endl;
Cout << Endl << "Information about vTable" << Endl << endl; cout << "value at 1st entry of vTable"
<< (int *) * (INT *) (INT *) (& objclass 0) 0) << ENDL;
Cout << "Value At 2nd Entry of VTABLE"
<< (int *) * (INT *) (& objclass 0) 1) << ENDL;
Return 0;
}
The result of the program output is:
Address of Virtual Pointer 0012FF7C
Value at Virtual Pointer I.E. Address of Virtual Table 0046C0EC
Information About VTABLE
Value at 1st Entry of vtable 0040100A
Value at 2nd entry of vTable 0040129E
Now a problem is naturally generated in the brain. How does the compiler know the length of the virtual function table? The answer is that the last entry point in the table is NULL. Change the program to make us understand this problem.
Program 12
#include
Using namespace std;
Class class {
Virtual void f () {cout << "Class :: f" << endl;
Virtual void g () {cout << "class :: g" << endl;
}
Int main () {
Class Objclass;
COUT << "Address of Virtual Pointer << (int *) (& objclass 0) << endl;
Cout << "Value At Virtual Pointer I. Address of Virtual Table"
<< (int *) * (INT *) (& objclass 0) << endl;
Cout << Endl << "Information about vTable" << Endl << Endl;
Cout << "Value At 1st Entry Of VTABLE"
<< (int *) * (INT *) (INT *) (& objclass 0) 0) << ENDL;
Cout << "Value At 2nd Entry of VTABLE"
<< (int *) * (INT *) (& objclass 0) 1) << ENDL;
Cout << "Value at 3rd Entry of VTABLE"
<< (int *) * ((int *) * (INT *) 2) << ENDL;
COUT << "Value at 4th Entry of VTABLE"
<< (int *) * (INT *) (INT *) (& objclass 0) 3) << Endl; Return 0;
}
The output result of the program is:
Address of Virtual Pointer 0012FF7C
Value at Virtual Pointer I.E. Address of Virtual Table 0046C134
Information About VTABLE
Value at 1st Entry of vtable 0040100A
Value at 2nd entry of vTable 0040129E
Value at 3rd Entry of vtable 00000000
Value at 4th Entry of VTable 73616C43
The output result indication of this program is NULL in the last entry point in the table. Let us call the virtual function.
Program 13
#include
Using namespace std;
Class class {
Virtual void f () {cout << "Class :: f" << endl;
Virtual void g () {cout << "class :: g" << endl;
}
TypeDef void (* fun) (VOID);
Int main () {
Class Objclass;
Fun Pfun = NULL;
// Calling 1st Virtual FUNCTION
PFUN = (fun) * ((int *) * (INT *) 0);
PFUN ();
// Calling 2nd Virtual Function
PFUN = (fun) * ((int *) * (INT *) (& objclass 0) 1);
PFUN ();
Return 0;
}
The output of this program is:
Class :: f
Class :: g
Now let's take a look at most inheritance. Let us see this simple multi-inheritance.
Program 14
#include
Using namespace std;
Class Base1 {
PUBLIC:
Virtual void f () {}
}
Class base2 {
PUBLIC:
Virtual void f () {}
}
Class Base3 {
PUBLIC:
Virtual void f () {}
}
Class Drive: Public Base1, Public Base2, Public Base3 {
}
Int main () {
Drive objdrive;
COUT << "size is =" << sizeof (objDrive) << Endl;
Return 0;
}
The output of this program is:
Size is = 12
This program describes that when you derive it from multiple base classes, this derived class has virtual function pointer for all base classes.
What happens when the derived class is ordered? Let's take a look at this program, better understand more inherited virtual function concept.
Program 15
#include
Using namespace std;
Class Base1 {
Virtual void f () {cout << "base1 :: f" << endl;}
Virtual void g () {cout << "base1 :: g" << endl;}};
Class base2 {
Virtual void f () {cout << "base2 :: f" << endl;}
Virtual void g () {cout << "base2 :: g" << endl;}
}
Class Base3 {
Virtual void f () {cout << "base3 :: f" << endl;}
Virtual void g () {cout << "base3 :: g" << endl;}
}
Class Drive: Public Base1, Public Base2, Public Base3 {
PUBLIC:
Virtual void fd () {cout << "drive :: fd" << endl;
Virtual void gd () {cout << "drive :: gd" << endl;
}
TypeDef void (* fun) (VOID);
Int main () {
Drive objdrive;
Fun Pfun = NULL;
// Calling 1st Virtual Function Of Base1
PFUN = (fun) * (INT *) (INT *) (INT *) & objDrive 0) 0);
PFUN ();
// Calling 2nd Virtual Function Of Base1
PFUN = (fun) * (INT *) (INT * & ObjDrive 0) 1);
PFUN ();
// Calling 1st Virtual Function Of Base2
PFUN = (fun) * (INT *) (INT *) & objDrive 1) 0);
PFUN ();
// Calling 2nd Virtual Function Of Base2
PFUN = (fun) * (INT *) (INT *) & objDrive 1) 1);
PFUN ();
// Calling 1st Virtual Function Of Base3
PFUN = (fun) * (INT *) (INT *) & objDrive 2) 0);
PFUN ();
// Calling 2nd Virtual Function Of Base3
PFUN = (fun) * ((int *) * (int *) & objDrive 2) 1);
PFUN ();
// Calling 1st Virtual Function Of Drive
PFUN = (fun) * (INT *) (INT *) & objDrive 0) 2);
PFUN ();
// Calling 2nd Virtual Function of Drive
PFUN = (fun) * (INT *) (INT * & ObjDrive 0) 3);
PFUN ();
Return 0;
}
The output of this program is:
Base1 :: f
Base1 :: g
Base2 :: f
Base2 :: f
Base3 :: f
Base3 :: f
Drive :: fd
Drive :: GD This program Description Inherited virtual function in the virtual function table pointed to by the first virtual function pointer.
We can get the offset of the assignment of the party via static_cast. Let us observe the following procedures, better understand it.
Program 16
#include
Using namespace std;
Class Base1 {
PUBLIC:
Virtual void f () {}
}
Class base2 {
PUBLIC:
Virtual void f () {}
}
Class Base3 {
PUBLIC:
Virtual void f () {}
}
Class Drive: Public Base1, Public Base2, Public Base3 {
}
// Any Non Zero Value Because Multiply Zero with any no is zero
#define some_Value 1
Int main () {
Cout << (dword) static_cast
Cout << (dword) static_cast
Cout << (dword) static_cast
Return 0;
}
ATL uses a macro called OffsetOfClass to complete this operation, the macro defines in the atldef.h header file. Macro definition is:
#define offsetofClass (Base, Derived) /
((DWORD) (static_cast
The macro returns the offset of the base class virtual function pointer (VPTR) in the distribution class object module. Let us look at an example to understand this.
Program 17
#include
#include
Using namespace std;
Class Base1 {
PUBLIC:
Virtual void f () {}
}
Class base2 {
PUBLIC:
Virtual void f () {}
}
Class Base3 {
PUBLIC:
Virtual void f () {}
}
Class Drive: Public Base1, Public Base2, Public Base3 {
}
#define _atl_packing 8
#define offsetofClass (Base, Derived) /
((DWORD) (static_cast
Int main () {
Cout << Offsetofclass (Base1, Drive) << Endl;
Cout << OffsetofClass (Base2, Drive) << ENDL;
Cout << Offsetofclass (Base3, Drive) << Endl;
Return 0;
}
The memory layout of the derived class is:
The output of this program is:
0
4
8
The output result of this program describes the offset of this macro returns the base class virtual function pointer (VPTR). In Don Box's COM Nature (Essential COM) it uses a similar macro to get offset. Adopt the program to replace the macro of the ATL with the macro of BOX. Program 18
#include
#include
Using namespace std;
Class Base1 {
PUBLIC:
Virtual void f () {}
}
Class base2 {
PUBLIC:
Virtual void f () {}
}
Class Base3 {
PUBLIC:
Virtual void f () {}
}
Class Drive: Public Base1, Public Base2, Public Base3 {
}
#define base_offset (Classname, BaseName) /
(DWORD (static_cast
(0x10000000)) - 0X10000000)
Int main () {
COUT << Base_offset (Drive, Base1) << Endl;
Cout << Base_offset (Drive, Base2) << Endl;
Cout << Base_offset (Drive, Base3) << Endl;
Return 0;
}
The program output results and use are the same as the previous program.
Let us use this macro in the program to do some practical things. In fact, we can call a specific base class vocal number by calling the base function virtual function pointer (VPTR) obtained in the derived memory structure.
Program 19
#include
#include
Using namespace std;
Class Base1 {
PUBLIC:
Virtual void f () {cout << "Base1 :: f ()" << endl;
}
Class base2 {
PUBLIC:
Virtual void f () {cout << "Base2 :: f ()" << endl;}
}
Class Base3 {
PUBLIC:
Virtual void f () {cout << "Base3 :: f ()" << endl;}
}
Class Drive: Public Base1, Public Base2, Public Base3 {
}
#define _atl_packing 8
#define offsetofClass (Base, Derived) /
((DWORD) (static_cast
Int main () {
Drive d;
Void * pvoid = NULL;
// Call Function Of Base1
Pvoid = (char *) & D OffsetOfClass (Base1, Drive);
((BASE1 *) (pvoid)) -> f ();
// Call Function of Base2
Pvoid = (char *) & D OffsetOfClass (Base2, Drive);
((Base2 *) (PVOID)) -> f ();
// Call Function Of Base1
Pvoid = (char *) & D OffsetOfClass (Base3, Drive);
((Base3 *) (PVOID)) -> f ();
Return 0;
}
The output of this program is:
Base1 :: f ()
Base2 :: f ()
Base3 :: f ()
In this tutorial, I explained the working principle of OffsetOfClass macro in ATL. In the next article I want to explore other mysterious things in ATL. It is inevitable that there is an error in the translation process, welcome criticism to correct! ! !