Author: Zeeshan Amjad Original link: http: //www.codeproject.com/atl/atl_underthehood_.asp
Introducing in this series of tutorials, I want to discuss some of the internal works of ATL and the technologies it use.
In the discussion, let's take a look at a program's memory distribution. First, write a simple program that does not have any data members, you can look at its memory structure. Procedure 1. # include
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 = 1address of object is = 0012ff7c Now, if we add some data members to the class, the size of this class will be the sum of each member. For templates, it is still like this: the program 2. # include
Template
INT main () {cpoint
Template
Template
INT main () {cpoint
CPOINT3D
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;} To: size of class = 4address of class = 0012ff7c, after we add more than one virtual function, it will become more interesting: program 5. # include
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;}) Output is exactly the same as the previous program, let us make an experiment to better understand this. Program 6. # include
Class cpoint {public: int m_ix; int m_iy; virtual ~ cpoint () {} // translation: Original here has a semicolon, I will remove it, all the same};
INT main () {cpoint objPoint; cout << "size of class = << sizeof (objPoint) << endl; cout <<" address of class = "<< & objPoint << endl; return 0;} To: size of class = 12Address of class = 0012ff68 The output of these programs indicates that after you add a virtual function to the class, then its size will add an int to the size. In Visual C is also an increase in 4 bytes. This means that there are three integers in this class, one is x, one is Y, the other is a virtual function table pointer to handle virtual functions. First, let's take a look at this new location, which is the virtual function table pointer in the object header (or end). To do this, we need to directly access the memory occupied by the object. We can use a magical pointer technology to store an address of an object with a pointer to int. Program 7. # include
Int main () {cpoint objPoint (5, 10);
INT * PINT = (int *) & objPoint; * (PINT 0) = 100; // Attempt to change the value of X * (PINT 1) = 200; // Attempt to change the value of Y
Cout << "x =" << objPoint.getx () << endl; cout << "y =" << objPoint.gety () << endl;
Return 0;} The most important thing in this program is: int * Pint = (int *) & objPoint; * (PINT 0) = 100; // attempt to change the value of X * (PINT 1) = 200; // Attempting to change the value of Y, after we put the object's address into a integer pointer, you can see this object as a integer pointer. The output of the program is: x = 200y = 10 Of course, this is not what we want, it indicates that 200 stored in the position of the M_IX data member. This means m_ix, that is, the first member variable, is the second location in the memory, not the first. In other words, the first member is a virtual function table pointer, and the other is the data member of the object. Then, only need to modify the following two lines: int * pint = (int *) & objPoint; * (PINT 1) = 100; // attempt to change the value of X * (PINT 2) = 200; // Attempt to change Y The value we gain the result, the following is the full program: program 8. # include
Int main () {cpoint objPoint (5, 10);
INT * PINT = (int *) & objPoint; * (PINT 1) = 100; // attempt to change the value of X * (PINT 2) = 200; // Attempt to change the value of Y
Cout << "x =" << objPoint.getx () << endl; cout << "y =" << objPoint.gety () << endl;
Return 0;} and, the output of the program is: x = 100y = 200 The picture below shows clearly demonstrated that after we add a virtual function to the class, the virtual function table pointer will be added in the first location in the memory structure. . Now there is a problem: What is stored in the virtual function table pointer? Take a look at the procedures below: Program 9. # include
Class class {virtual void fun () {cout << "class :: fun" << endl;}};
INT main () {class objclass;
COUT << "Address of Virtual Pointer << (INT *) << Endl; Cout <<" Value At Virtual Pointer << (INT *) * (INT *) (& Objclass 0) <
TypeDef void (* fun) (VOID);
INT main () {class objclass;
COUT << "Address of Virtual Pointer << (INT *) << Endl; Cout <<" Value At Virtual Pointer IE Address of Virtual Table "<< (INT *) * (INT *) & objclass 0) << endl; cout << "Value at first entry of var-" << (int *) * (int *) * (INT *) (& objclass 0) << Endl; cout << endl < <"EXECUTING VIRTUAL FUNCTION" << Endl << endl; fun pfun = (fun) * (int *) * (& objclass 0); pfun (); return 0;} Some of this program is used. Indirect calls for type conversion, the most important lines are: fun pfun = (fun) * (int *) * (INT *); Here, Fun is a function pointer type defined by TypeDef: typedef Void (* fun) (Void); let us analyze this lengthy indirect call. (INT *) (& ObjClass 0) gives the address of the virtual function table pointer, this virtual function table pointer is the first entry of the class, and we convert it to int *. To get the value of this address, we need to use the indirect call operator (ie *), then convert it to int *, which is (int *) * (int *) (& objclass 0). This will give the first entry of the virtual function table. To get the value of this location, that is, get the address of the first virtual function in the class, we need to use the indirect call operator again, convert it to the appropriate function pointer type, so fun pfun = (fun) * (int *) * (INT *) (& objclass 0); represents the first entry from the virtual function table to obtain a value of FUN, and store it in PFUN. So, how will it be more than one virtual function? Now we want to access the second virtual functions in the virtual function table, please see the following procedures: program 11. # include
INT main () {class objclass;
COUT << "Address of Virtual Pointer << (INT *) << Endl; Cout <<" Value At Virtual Pointer IE 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 *) * (INT *) 1) << endl; return 0 The output of the program is: Address of Virtual Pointer 0012FF7cValue At Virtual Pointer IE Address Of Virtual Table 0046C0EC
Information About VTABLE
Value at 1st entry of vTable 0040100AValue at 2nd entry of vtable 0040129e So, one problem naturally appears - how does the compiler know the length of the virtual function table? The answer is: The last entry of the virtual function table is NULL. You can consider this problem with the program. Program 12. # include
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 *) << Endl; Cout <<" Value At Virtual Pointer IE 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 *) * (INT *) 1) << Endl; Cout < <"Value At 3rd Entry Of VTABLE << (int *) * (INT *) (& objclass 0) 2) << Endl; cout <<" Value at 4th Entry of vTable "< <(int *) * (INT *) (& objclass 0) 3) << Endl; Return 0;} Output is: Address of Virtual Pointer 0012FF7CVALUE AT Virtual Pointer IE Address of Virtual Table 0046C134
Information About VTABLE
Value At 1st Entry Of Entry Of VTABLE 0040129EVALUE AT 3rd Entry Of VTABLE 000000008 Value At 4th Entry Of VTABLE 73616C43 This program is output to demonstrate the last entry of the virtual function table is NULL. Let us call the virtual function with existing knowledge: program 13. # include
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;
// Call the first virtual function PFUN = (FUN) * ((int *) * (INT *) 0); PFUN ();
// Call the second virtual function PFUN = (FUN) * ((int *) * (INT *) 1); PFUN ();
Return 0;} The output of the program is: Class :: fclass :: g Let's take a look at multiple inheritance. First look at the simplest inheritance: program 14. # include
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 the program: SIZE IS = 12 This program demonstrates that when you inherit a class from multiple base classes, this derived class will have virtual function table pointers for all base classes. So, what happens when derived class also has a virtual function table pointer? Let's take a look at the procedures below to understand the concept of multiple inheritance with virtual functions. Program 15. # include
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" < TypeDef void (* fun) (VOID); Int main () {drive objDrive; Fun Pfun = NULL; // Call the first virtual function PFUN = (fun) * (int *) * (int *) * (int *) * (int *) * (int *) * (int *); (int *) * (INT *); PFUN (); // Call the second virtual function PFUN = (fun) * (INT *) * (INT *) * (INT *) * (INT *) * (INT *) * (INT *); PFUN (); // Call the first virtual function PFUN = (fun) * (int *) * (int *) * (int *) * (int *) * (int *) * (INT *); PFUN (); / / Call the second virtual function PFUN = ((int *) * (int *) (INT *) * (int *) * (int *) * (int *) * (int *) * (int *) * (INT *); PFUN (); // Call the first virtual function PFUN = (fun) * (INT *) * (INT *) * (INT *) * (INT *) * (INT *); PFUN (); // Call the second virtual function pfun = (fun) * (int *) * (int *) * (int *) * (int *) * (int *) * (int *) * (int *) * (int *) * (INT *); pfun () // Call the first virtual function pfun = (fun) * (int *) * (int *) * (int *) (INT *) * (INT *) * (int *) * (int *); PFUN (); // call derived class Second virtual function pfun = (fun) * (int *) * (int *) & objDrive 0) 3); PFUN (); Return 0;} The output is: Base1 :: fbase1 :: gbase2 :: fbase2 :: fbase3 :: fbase3 :: fdrive :: fddrive :: GD This program demonstrated the virtual function of the derived class is stored in the first virtual In the function table. We can get the offset of the derived class virtual function table pointer by using Static_cast, see the following procedures: program 16. # include 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 {}; // Arbitrary non-0 value, because 0 multiply any number of 0 # define some_value 1 INT main () {cout << (dword) static_cast 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;} derived class The memory layout is: The output of the program is: 048 The output of this program demonstrates this macro to return the virtual function table pointer offset of the specified base class. In Don Box's "COM Nature", it uses a simple macro that you can modify this program, replace the ATL macro with the macro of the box. Program 18. # include 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 INT main () {cout << Base_offset (drive, base1) << endl; cout << base_offset (drive, base2) << endl; cout << base_offset (drive, base3) << endl; return 0;} The purpose and output of the program are identical to the previous program. Now let's use this macro to make some special things. In fact, we can call the virtual function in the specified base class by getting the offset of the base class virtual function table pointer in the derived memory structure. Program 19. # include 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 the function pvoid = (char *) & d offsetofclass ((base1 *) ((pvoid)) -> f (); // Call Base2 function pvoid = (char *) & D Offsetofclass (Base2, Drive); ((Base2 *) (PVOID)) -> f (); // Call the function of Base3 (Translation: Original is Base1) Pvoid = (char *) & D OffsetOfClass (Base3, Drive); ((Base3 *) (PVOID)) -> f (); RETURN 0;} The output of the program is: base1 :: f () base2 :: f () base3 :: f () In this chapter, I tried to explain how the OFFSTOFClass macro in the ATL. I hope to continue to explore other secrets in ATL in the next article.