The secret of the secret of the ATL cloth

xiaoxiao2021-03-06  39

Introduction

In this series of tutorials, I want to discuss some of the internal works of ATL and the technology it use, which is the second article of this series.

Now let us explore some more interesting information behind the virtual function. In order to maintain consistent with the above, in the discussion of this article, I will use the same order, and the serial number of the program starts from 20.

Let's take a look at this program:

Program 20.

#include 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 (); returnograph

The output of the program is: drive :: fun

This program clearly demonstrates how the function of the base class is how to call the virtual function of the derived class. This technology is used in different frameworks, such as MFC and design modes (such as Template Design Pattern). Now you can modify this program to see its behavior, I will call the virtual function in the constructor of the base class, not ordinary member functions.

Program 21.

#include 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 the program is: ASE :: fun

This program shows that we cannot call derived virtual functions in the constructor of the base class. Ok, let's take a look at what I did in the bottom of the cloth. I will print the pointer value among these constructors. For the sake of simplicity, I removed other functions in the class.

Program 22.

#include using namespace std; class base {public: base () {cout << "in base" << endl; cout << "this pointer =" << (int *) this << endl; cout <

In basethis Pointer = 0012FF7CIN drivethis Pointer = 0012FF7CIN Main0012FF7C

This means that only one object is present in the entire memory location. Then let's print this pointer to the value, that is, the value of the pointer VPTR pointing to the virtual function table, the address of the VTable.

Program 23.

#include 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 <

In Basevirtual Pointer = 0012FF7CADDRESS OF VTABLE = 0046C08cValue AT VTABLE = 004010f0in DriveVirtual Pointer = 0012FF7CADDRESS OF VTABLE = 0046C07CVALUE AT VTABLE = 00401217

This program demonstrates different virtual function table addresses in the base class and derived class. In order to better understand this problem, let us deepen the inheritance level and add a MostDrive class inherited in the Drive class and build an object.

Program 24.

#include 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;}}; int main () {MostDrive D; return 0;} The output of the program is:

In BaseVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0A0Value at Vtable = 004010F5In DriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C090Value at Vtable = 00401221In MostDriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C080Value at Vtable = 00401186

This program demonstrates the initialization process in the constructor of the virtual function table pointer in each class. In this way, the address of the virtual function table in each class constructor is different, and the main function uses the most bottom derived class in the inheritance chain to create an object.

Now you can look at the location of the constructor of each class in the virtual function table, you can store the first entry in the virtual function table into a function pointer and try running it.

The program 25.

#include using namespace std; typedef void (* fun) (); class base {public: base () {cout << "in base" << endl; cout << "Virtual Pointer =" << (int *) ^ << 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 (); couid f1 ()} Virtual Void F1 () {cout << " Base :: f1 "<< Endl;}}; Class Drive: public base {public: drive () {cout <<" in driving << endl; cout << "Virtual Pointer =" << (int *) << 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 (); couid :: {cout << "drive :: F1 "<< Endl;}}; Class MostDrive: PUBLIC DRIVE {public: MostDrive () {cout <<" in MostDrive "<< Endl; cout <<" Virtual Pointer = << (int *) {e COUT << "address of vtable =" << (int *) * (int *) this << Endl; cout << "V Alue at vtable = "<<

(int *) * (int *) This << Endl; fun pfun = (fun) * (int *) * (int *) this; pfun (); couid f1 () {cout << "MostDrive :: f1" << endl;}}; int main () {MostDrive D; Return 0;} The output of the program is:

In BaseVirtual Pointer = 0012FF7CAddress of Vtable = 0046C098Value at Vtable = 004010F5Base :: f1In DriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C088Value at Vtable = 00401221Drive :: f1In MostDriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C078Value at Vtable = 00401186MostDrive :: f1

This program demonstrates how the constructor in each class uses its own virtual function to fill the entrances in the virtual function table. So, the Base class uses the virtual function address of the Base class to fill your own virtual function table. When the DRIVE class constructor is executed, another virtual function table will be created and store its own virtual function address.

Now, if you find multiple virtual functions in the base class, derived classes do not fully rewrite them.

Program 26.

#include 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; couid f1 ()} Virtual Void F1 () {cout <<" Base :: F1 "<< Endl;} Virtual Void F2 () {cout <<" Base :: F2 "<< Endl;}}; Class Drive: Public Base {public: drive () {cout <<" in driving "<< 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 the program:

In BaseVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0E0Value at Vtable 1st entry = 004010F0Value at Vtable 2nd entry = 00401145Value at Vtable 3rd entry = 00000000In DriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0C8Value at Vtable 1st entry = 0040121CValue at Vtable 2nd entry = 00401145Value at Vtable 3rd Entry = 00000000

The output of this program indicates that the virtual function of the base class is not overwritten in the derived class, then the derived class constructor does not do anything about the entrance to the virtual function.

So now, let me invite the pure virtual function to join this game, then take a look at its behavior. Please see the following procedures:

Program 27.

#include 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 *) t 0) << endl; cout << "Value AT vTable 2nd entry = << (int *) * (int *) * (int *) THIS 1) << endl; cout << } Virtual Void F1 () = 0; Virtual Void F2 () = 0;}; Class Drive: Public Base {public: drive () {cout << "in driving" <" << (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 *) THIS 1) << Endl; cout << Endl;} Virtual void f1 () {cout << "drive :: f1" << Endl;} Virtual void f2 () {cout << "drive :: f2" <

In BaseVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0BCValue at Vtable 1st entry = 00420CB0Value at Vtable 2nd entry = 00420CB0In DriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0A4Value at Vtable 1st entry = 00401212Value at Vtable 2nd entry = 0040128F The following is the output mode of release:

In BaseVirtual Pointer = 0012FF80Address of Vtable = 0042115CValue at Vtable 1st entry = 0041245DValue at Vtable 2nd entry = 0041245DIn DriveVirtual Pointer = 0012FF80Address of Vtable = 00421154Value at Vtable 1st entry = 00401310Value at Vtable 2nd entry = 00401380

In order to better understand this principle, we need to make a little change to the program and try to use the function pointer to call the virtual function.

Program 28.

#include using namespace std; typedef void (* fun) (); class base {public: base () {cout << "in base" << endl; cout << "Virtual Pointer =" << (int *) @< 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 perform the first virtual function fun pfun = (fun) * (int *) * (int *) THIS 0); PFUN (); couid f1 ()} Virtual Void F1 () = 0; Virtual Void F2 () = 0;}; Class Drive: public base {public: drive () {cout << "in driving" << endl; cout << "Virtual Pointer =" << (int *) This << endl; cout << "address of vTable =" << (int *) * (int *) this << Endl; cout << "value at vTable 1st entry = << (int *) * ((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 behavior of the program is still different in debug and release mode. In Debug mode, it displays a dialog box for runtime errors:

And, when you press the "Ignore" button, it will display the following dialog: and if you run in Release mode, it will only output an error message in the console window:

In Basevirtual Pointer = 0012FF80ADDRESS OF VTABLE = 0042115cValue AT vTable 1st entry = 0041245dValue AT vTable 2nd entry = 0041245druntime Error R6025- Pure Virtual Function Call

So what is the R6025 here? It is defined in the CMSGS.h header file, which defines all the error messages of all C Run Time Library.

#define _RT_Purevirt_txt "R6025" EOL "- Pure Virtual Function Call" EOL

In fact, when we define the pure virtual function, the compiler places a function address called _pureCall's C Run Time Library. This function is defined in purevirt.c, its prototype is as follows:

Void __cdecl _purecall (void); // Translation: Original here No semicolon

We can call this function directly to reach the same effect, please see the following applet:

Program 29.

INT main () {_purecall (); return 0;

This program is the same as the output in Debug mode and Release mode. In order to better understand this problem, let us make a deeper inheritance chain, and inherit a class from the DRIVE class to see the effect.

Program 30.

#include 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 *) t 0) << endl; cout << "Value AT vTable 2nd entry = << (int *) * (int *) * (int *) THIS 1) << endl; cout << } Virtual Void F1 () = 0; Virtual Void F2 () = 0;}; Class Drive: Public Base {public: drive () {cout << "in driving" << 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 *) 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 *) THI s << 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 the program is:

In BaseVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0D8Value at Vtable 1st entry = 00420F40Value at Vtable 2nd entry = 00420F40In DriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0C0Value at Vtable 1st entry = 00420F40Value at Vtable 2nd entry = 00420F40In MostDriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0A8Value at VTable 1st entry = 00401186Value AT vTable 2nd entry = 004010f5

This program shows that Base and Drive classes use the same value to initialize their virtual functions. Then, if inheritance is deeper, and only the most underlying derived class has rewritten the pure virtual function, what happens in this case? This is what happened in the case of COM program - the interface is a class that only has a pure virtual function, and an interface is inheriting from another interface, and only the type of implementation will rewrite the pure virtual function of the interface. In this way, the constructor of each base class will initialize their own virtual function table inlet with the same value. So, this means that the same code will repeat it.

The main thinking of ATL is to make COM components as small as possible, but because this line is, the interface class constructor will have a lot of unnecessary code. In order to solve this problem, ATL introduces a macro ATL_NO_VTABLE, which is defined in Atldef.h:

#define ATL_NO_VTABLE __DECLSPEC (NOVTABLE)

__Declspec (NOVTABLE) is a class attribute that is the Microsoft C extension. It will make the compiler do not generate the code of the initialization virtual function table pointer and virtual function table, which reduces the size of the generated code.

So, let's modify our code, so you can understand what this property can do for us.

Program 31.

#include 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 *) t 0) << endl; cout << "Value AT vTable 2nd entry = << (int *) * (int *) * (int *) THIS 1) << endl; cout << } Virtual Void F1 () = 0; Virtual Void F2 () = 0;}; Class Drive: Public Base {public: drive () {cout << "in driving" <" << (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 *) 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 the program is:

In BaseVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0CCValue at Vtable 1st entry = 00420E60Value at Vtable 2nd entry = 00420E60In DriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0B4Value at Vtable 1st entry = 00420E60Value at Vtable 2nd entry = 00420E60In MostDriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0B4Value at Vtable 1st entry = 00420E60Value at vTable 2nd entry = 00420E60

This program has another result, which is the same virtual function table address, but the Base class is different from the DRIVE and MOSTDRIVE class. In fact, this is because we don't have the use of the __DECLSPEC (NOVTABLE) attribute for the Base class. Now, let's use the same properties to the inherited Drive class, that is, __DECLSPEC (NOVTABLE).

Program 32.

#include 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 *) t 0) << endl; cout << "Value AT vTable 2nd entry = << (int *) * (int *) * (int *) THIS 1) << endl; cout << } Virtual Void F1 () = 0; Virtual Void F2 () = 0;}; Class __Declspec (NOVTABLE) DRIVE: PUBLIC BASE {public: drive () {cout << "in driving" << 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; couid f1 ()} Virtual void f1 () {COUT < <"MOSTDRIVE :: F1" << Endl;} Virtual Void F2 () {cout << "MostDrive :: F2" << endl;}}; int main () {MostDrive D; return 0;} Now, program The output is:

In BaseVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0C0Value at Vtable 1st entry = 00420E50Value at Vtable 2nd entry = 00420E50In DriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0C0Value at Vtable 1st entry = 00420E50Value at Vtable 2nd entry = 00420E50In MostDriveVirtual Pointer = 0012FF7CAddress of Vtable = 0046C0C0Value at Vtable 1st entry = 00420E50Value at vTable 2nd entry = 00420E50

In MSDN, the interpretation of __declspec (novTable) is: it should be used in a purely deficiency class. So, let us make an experiment to better understand this meaning.

Program 33.

#include 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 *) t 0) << endl; cout << "Value AT vTable 2nd entry = << (int *) * (int *) * (int *) THIS 1) << endl; cout << } Virtual Void F1 () = 0; Virtual Void F2 () = 0;}; Class __Declspec (NOVTABLE) DRIVE: PUBLIC BASE {public: drive () {cout << "in driving" << 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 to call the 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;} Our new things we added in the program are:

// Try to call the first virtual function typedef void (* fun) (); fun pfun = (fun) * (int *) * (int *) THIS 0); PFUN ();

And, when we run this application, we will face the same problem as the previous program - that is, try to call the error occurred in the virtual function. This means that the virtual function table is not initialized. The MostDrive class is not an abstract class, so we should remove __declspec (novTable) from the class.

Program 34.

#include 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 to call the 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, the program can work normally. Its output is: MostDrive :: F1

This attribute does not necessarily use only in the ATL class, which can be used for any class that cannot be created. Similarly, it is not necessarily used in the ATL class, that is, it can be omitted from the ATL class, but this means that there will be more code.

I hope to explore more ATL secrets in the next article.

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

New Post(0)