Directly call the class member function address

Source: Internet
Author: User

Abstract: This section describes how to obtain the member function address and call the address.

Keyword: C ++ member function this pointer call Convention

I. Usage of member function pointers

In C ++, the pointer to a member function is special. For common function pointers, it can be regarded as an address and can be converted and called directly as needed. However, for member functions, regular type conversion cannot be compiled, and special syntax must be used for calling. C ++ has prepared three operators for member pointers: ": *" for pointer declaration, and "-> *" and ". * "is used to call the function pointed to by the pointer. For example:

Class TT {public: void Foo (int x) {printf ("\ n % d \ n", x) ;}}; typedef void (TT: * functype) (INT); functype PTR = TT: Foo; // assign a value to a member function pointer. tt a; (. * PTR) (5); // call the member function pointer. TT * B = new TT; (B-> * PTR) (6); // call the member function pointer.

 

Note the usage of parentheses when calling function pointers. Because the priority of. * And-> * is relatively low, you must put the elements to be combined with the two sides in a bracket; otherwise, compilation will fail. Not only that, but more importantly, it is impossible to perform any type conversion for the member function pointer, for example, if you want to save the address of a member function to an integer (that is, to obtain the address of a member function of the class), The type conversion method cannot be used. the following code:

    DWORD dwFooAddrPtr= 0;    dwFooAddrPtr = (DWORD) &tt::foo;  /* Error C2440 */    dwFooAddrPtr = reinterpret_cast (&tt::foo); /* Error C2440 */

You only get two c2440 errors. Of course, you cannot convert the member function type to any other slightly different type. Simply put, each member function pointer is a unique type and cannot be converted to any other type. Even if the definitions of the two classes are completely the same, they cannot be converted between their corresponding member function pointers. This is a bit similar to the struct type. Each struct is a unique type, but the difference is that the struct pointer type can be forcibly converted. With these special usage and strict restrictions, the pointer of the class member function is actually useless. This is why we usually cannot see ": *", ". *" and "-> *" in the code.

2. Obtain the address of the member function

Of course, by referencing a master, "in windows, we always have a solution ". Similarly, in C ++, we always have a solution. This problem has been solved for many years and is widely used (in MFC ). Generally, there are two methods: one is to use the embedded assembly language to directly retrieve the function address, and the other is to use the Union type to escape the type conversion detection of c ++. Both methods use a certain mechanism to escape the type conversion detection of c ++. Why does the C ++ compiler simply leave this restriction open and let Programmers take the lead? Of course there is a reason, because the class member functions are different from normal functions. After conversion is allowed, errors are very easy. This will be detailed later. Now let's take a look at two methods to get the class member function address:

Method 1:

template void GetMemberFuncAddr_VC6(ToType& addr,FromType f){    union     {FromType _f;ToType   _t;    }ut;    ut._f = f;    addr = ut._t;}

This method is used as follows:

DWORD dwaddrptr;

Getmemberfuncaddr_vc6 (dwaddrptr, & TT: Foo );

Why use templates? Well, if you don't use a template, what should you do with the second parameter? Writing function pointers is not too cumbersome. The key is that there is no universality. Each member function must write a conversion function separately.

Method 2:

#define GetMemberFuncAddr_VC8(FuncAddr,FuncType)\{                                               \    __asm                                       \    {                                           \        mov eax,offset FuncType                 \    };                                          \    __asm                                       \    {                                           \        mov FuncAddr, eax                       \    };                                          \}

This method is used as follows:

DWORD dwAddrPtr;GetMemberFuncAddr_VC8(dwAddrPtr,&tt::foo);

I originally wanted to write a template function. Unfortunately, though compiled, it cannot run correctly. It is estimated that the use of template parameters in the assembly code is not very useful, and the offset is used to take the offset directly and the value is 0.
The macro above can run correctly, and an additional benefit is that the address of the Private member function can be directly obtained (probably in the ASM brackets, the compiler no longer checks the code accessibility ). However, it cannot be compiled in vc6 and can only be used in vc8.

Iii. Call the member function address

Through the above two methods, we can get the address of the member function. However, if you cannot call a member function through an address, it is useless. Of course, this is feasible. However, before that, you need to know some knowledge about member functions.
We know that the biggest difference between a member function and a common function is that a member function contains a hidden parameter, this pointer, which indicates that the member function currently works on that object instance. According to the call Convention (calling convention), the membership function implements the this pointer in different ways. If the _ thiscall call Convention is used, the this pointer is stored in the register ECx. This is the case in the VC compiler by default. If it is a _ stdcall or _ cdecl call convention, this pointer will be passed through the stack, and this pointer is the last parameter pushed into the stack, this parameter is added to the left of the parameter list of the function by the compiler.
Here is another thing to mention. Although VC regards the _ thiscall type as the default type of the member function, the _ thiscall keyword is not defined in vc6! If you use _ thiscall to define a function, the compiler reports the following error: '_ thiscall' keyword reserved for ure use.

Knowing this is easy. We only need to prepare the this pointer according to different call conventions, and then use the member function address like a normal function pointer.

For a member function of the _ thiscall type (note that this is the default type of VC), we add mov ECx, this; before calling the function, we can call the member function pointer. For example:

Class TT {public: void Foo (int x, char C, char * s) // No specified type. The default value is _ thiscall. {printf ("\ n M_a = % d, % d, % C, % s \ n", M_a, X, C, S) ;}int M_a ;}; typedef void (_ stdcall * functype) (int x, char C, char * s); // defines the corresponding non-member function pointer type. Specify _ stdcall. tt abc; ABC. m_a = 123; dword ptr; DWORD this = (DWORD) & ABC; getmemberfuncaddr_vc6 (PTR, TT: Foo); // obtain the member function address. functype fnfooptr = (functype) PTR; // converts the function address to a pointer of a common function. _ ASM // prepare the this pointer. {mov ECx, this;} fnfooptr (5, 'A', "7xyz"); // call the member function address like a common function.

For other types of member functions, we only need to declare a common function pointer similar to the original member function definition, but add a void * parameter to the leftmost of the parameters. The Code is as follows:

Class TT {public: void _ stdcall Foo (int x, char C, char * s) // The member function specifies the _ stdcall call convention. {printf ("\ n M_a = % d, % d, % C, % s \ n", M_a, X, C, S) ;}int M_a ;}; typedef void (_ stdcall * functype) (void * This, int X, char C, char * s); // note that a void * parameter is added. tt abc; ABC. m_a = 123; dword ptr; getmemberfuncaddr_vc6 (PTR, TT: Foo); // obtain the member function address. functype fnfooptr = (functype) PTR; // converts the function address to a pointer of a common function. fnfooptr (& ABC, 5, 'A', "7xyz"); // call the member function address like a normal function. Note that the first parameter is the this pointer.

Every time you define a function type and perform a forced conversion, this is annoying. Can you write these operations into a function, then you can specify the function address and parameters for each call? Yes, of course, and I have already written such a function.

// Call a class member function // callflag: the agreed call type of the member function, 0 -- thiscall, not 0 -- other types. // funcaddr: member function address. // This: the address of the Class Object. // count: Number of member function parameters. //...: parameter list of member functions. DWORD callmemberfunc (INT callflag, DWORD funcaddr, void * This, int count ,...) {DWORD re; If (count> 0) // The parameter is pushed to the stack. {__ ASM {mov ECx, count; // number of parameters, ECx, and cyclic counter. moV edX, ECx; SHL edX, 2; add edX, 0x14; edX = count * 4 + 0x14; next: Push dword ptr [EBP + EDX]; sub edX, 0x4; Dec ECx jnz next ;}// process this pointer. if (callflag = 0) // _ thiscall, default member function call type of VC. {__ ASM mov ECx, this;} else // _ stdcall {__ ASM push this ;:__ ASM // call the function {call funcaddr; MoV re, eax ;} return re ;}

When using this function, the preceding two calls can be written as follows:

Callmemberfunc (0, ptr1, & ABC, 3, 5, 'A', "7xyz"); // The first parameter is 0, which indicates that _ thiscall is used.

Callmemberfunc (1, ptr2, & ABC, 3, 5, 'A', "7xyz"); // The first parameter 1 indicates that the call is performed using non _ thiscall.

It should be noted that callmemberfunc has many restrictions, and it cannot generate a correct call sequence for all situations. One of the reasons is that it assumes that each parameter uses a 4-byte stack space. This is correct in most cases, such as the pointer, Char, short, Int, long, and the corresponding unsigned type, all these parameters use a 4-byte stack space. However, in many cases, the parameter does not use 4-character stack space, such as double, custom structure or class. although float occupies 4 bytes, the compiler also generates some floating point commands, which cannot be simulated in callmemberfunc. Therefore, the float parameter cannot be used.
To sum up, you can use callmemberfunc to call the function address if all the parameters of a member function are of integer compatible type. If not, define the corresponding common function type, force convert, prepare the this pointer, and then call the common function pointer.

Iv. Further Discussion

So far, we have discussed how to obtain the address of the member function and then how to use this address. However, some important cases are not discussed. We know that member functions can be divided into three types: common member functions, static functions, and virtual functions. What's more important is how it works with inheritance or even multi-inheritance.

First look at the simplest single inheritance, non-virtual functions.
 

class tt1{public:void foo1(){ printf("\n hi, i am in tt1::foo1\n"); }};class tt2 : public tt1{public:void foo2(){ printf("\n hi, i am in tt2::foo2\n"); }};

Note that the foo1 function is not defined in TT2, and its foo1 function is inherited from TT1. In this case, what happens when we directly retrieve the TT2: foo1 address row?

DWORD tt2_foo1;tt1 x;GetMemberFuncAddr_VC6(tt2_foo1,&tt2::foo1);CallMemberFunc(0,tt2_foo1,&x,0); // tt2::foo1 = tt1::foo1

The running result indicates that everything is normal! When we write TT2: foo1, the compiler knows that it is actually TT1: foo1, so it will secretly replace it. The code generated by the compiler (vc6) is as follows:

Getmemberfuncaddr_vc6 (tt2_foo1, & TT2: foo1); // source code. // The assembly code generated by the vc6 compiler: Push offset @ ILT + 235 (TT1: foo1) (004010f0) // replace TT2: foo1. directly with TT1: foo1 ....

Let's take a look at the virtual functions that are inherited in a slightly more complex situation.

Class TT1 {public: void foo1 () {printf ("\ n hi, I am in TT1: foo1 \ n");} virtual void foo3 () {printf ("\ n hi, I am in TT1: foo3 \ n") ;}}; class TT2: Public TT1 {public: void foo2 () {printf ("\ n hi, I am in TT2: foo2 \ n");} virtual void foo3 () {printf ("\ n hi, I am in TT2 :: foo3 \ n ") ;}}; currently, both TT1 and TT2 define the virtual function foo3. According to the C ++ syntax, if foo3 is called through a pointer, polymorphism should occur. The following code: DWORD tt1_foo3, tt2_foo3; getmemberfuncaddr_vc6 (tt1_foo3, & TT1: foo3); then (tt2_foo3, & TT2: foo3); TT1 X; TT2 y; callmemberfunc (0, tt1_foo3, & X, 0); // TT1: foo3callmemberfunc (0, tt2_foo3, & X, 0); // TT2: foo3callmemberfunc (0, tt1_foo3, & Y, 0); // TT1: foo3callmemberfunc (0, tt2_foo3, & Y, 0); // TT2: foo3

The output is as follows:

hi, i am in tt1::foo3hi, i am in tt1::foo3hi, i am in tt2::foo3hi, i am in tt2::foo3

Note that for the second row of output, tt2_foo3 takes & TT2: foo3, but because the passed this pointer produces & X, TT1: foo3 is actually called. Similarly, the third row outputs the function address of the base class. However, because the actual object is a derived class, the function of the derived class is called. This indicates that the obtained member function address remains correct when the virtual function is used.
If you really understand what you mentioned above, it will be strange. When the function address is obtained, an integer (member function address) is obtained. Why are different functions entered during the call? As long as you look at the assembly code, it is clear, "No secret before the source code ". Source code: getmemberfuncaddr_vc6 (tt1_foo3, & TT1: foo3); The compilation code is as follows:

push offset @ILT+90(`vcall') (0040105f)...

When we used to get the TT1: foo3 address, we didn't actually pass the TT1: foo3 address to the function, but passed the address of a vcall function. Vcall, as its name implies, is of course a virtual call. We can find the address 0040105f to see what this function has done.

@ILT+90(??_9@$BA@AE):0040105F jmp `vcall' (00401380)

This address is only an item of ILT and jumps directly to the real vcall function, 00401380. Find 00401380 and you will see the vcall code.

'Vcall': 00401380 mov eax, dword ptr [ECx]; // treats this pointer as the DWORD type and points to the content (the first DWORD of the object) put eax.00401382 jmp dword ptr [eax]; // jump to the address pointed to by eax.

During code execution, ECx is the this pointer, specifically the address of the above object X or Y. And eax is the first DWORD Value of the object X or Y. We know that for class objects with virtual functions, the first address of the object is always a pointer, which points to the address table of a virtual function. The above object only has one virtual function, so there is only one virtual function table. Therefore, you can directly jump to the address pointed to by eax. If multiple virtual functions exist, eax must add an offset to locate different virtual functions. For example, if there are two virtual functions, two vcall codes correspond to different virtual functions. The code is roughly as follows:

`vcall':00401BE0 mov eax,dword ptr [ecx]00401BE2 jmp dword ptr [eax]`vcall':00401190 mov eax,dword ptr [ecx]00401192 jmp dword ptr [eax+4]

The compiler uses the corresponding vcall address instead of the virtual function address.

To sum up, the member function address obtained using the previous method remains correct when the virtual function is used because the compiler actually passes the corresponding vcall address. The vcall code locates the corresponding virtual function table based on the context this pointer, and then calls the correct virtual function.
Finally, let's take a look at the multi-inheritance situation. Obviously, the current situation is much more complicated. If you try it, you will encounter many difficulties. First, a conflict may occur when a member function is specified. Second, it must be adjusted when the this pointer is given. In addition, virtual inheritance may have to be specially processed. Solving all these problems is beyond the scope of this article, and the member function pointer I want is a real pointer. In the case of multiple inheritance, in many cases, the member function pointer has become a struct (see references), so it is extremely difficult to call the pointer correctly. The conclusion is that the method discussed above is not applicable to multi-inheritance. to directly call the member function address under multi-inheritance, you must manually handle various adjustments without a simple unified method.

Original post address http://www.vckbase.com/document/viewdoc? Id = 1818

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.