ATL Under the Hood - Part 1 (translation)

xiaoxiao2021-03-06  39

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 Objpoint;

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 Objpoint;

COUT << "SIZE OF Object Point IS =" << sizeof (objPoint) << ENDL;

COUT << "Address of object point is =" << & objPoint << Endl;

CPOINT3D objPoint3d;

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 ((drive *) some_value) -Some_Value << endl;

Cout << (dword) static_cast ((drive *) some_value) -Some_Value << endl

Cout << (dword) static_cast ((drive *) some_value) -Some_Value << endl;

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 ((Derived *) _ ATL_PACKING)) -_ atl_packing)

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 ((Derived *) _ ATL_PACKING)) -_ atl_packing)

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 (reinterpret_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 ((Derived *) _ ATL_PACKING)) -_ atl_packing)

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! ! !

转载请注明原文地址:https://www.9cbs.com/read-83062.html

New Post(0)