Dynamic invocation of variable parameter functions

Source: Internet
Author: User

Recently, a strange question has been encountered: How to invoke a variable parameter function dynamically in a function. For example, there is a variable parameter function:

void Func1 (int a, ...) { ... }

Now we give an indefinite number of dynamic arrays and pass them into the FUNC1 function as variable parameters in order.

Of course, if you allow changes to the definition of Func1, then I believe that everyone can do this task easily, and the method must be eight Immortals crossing, recount. But if the qualification does not change the definition of Func1, then this problem will be troublesome. After looking at all of the C + + grammar data on hand, only stunned to find that the problem in the C + + syntax of the specification, is no solution. So, in order to solve this problem, there is only one way to do it: Use the ultimate trick of C + +: Embed the assembly code.

Speaking of, embedded assembly, many people will find it very abstruse, difficult to understand. But careful analysis, in fact, is not unattainable. Back to this problem, the root cause of this problem is: in C + + syntax, the pressure stack, call, Cleanup stack, and get return value of a function call must be done in a C/C + + statement. Therefore, any attempt to simulate a loop by looping or macro expansion is unlikely to accomplish this. Therefore, we embed the purpose of the Assembly is very clear: is to use the assembly to implement a function call process.

In C + + functions, we can manually specify 3 different calling conventions: __stdcall, __cdecl, __fastcall. On the similarities and differences of these kinds of calling conventions, the online article is already a voluminous, abound, here will no longer continue to repeat, but it is worth pointing out that only __cdecl can support variable parameters, so we only need to implement the process of this calling convention.

__CDECL's calling convention is actually not complicated, in short, from right to left stack, caller recovery (cleanup) stacks. We can first construct a simple function call code to see:

void Func3 (int a, int b);
void Func4 (void)
{
__asm
{
Push 2/Press into the second parameter
Push 1//press into first parameter
Call FUNC3//Calling function
Add ESP, 8//restore the pointer to the top of the stack
}
}

It doesn't look complicated, the order of the parameter stacks is from right to left, and call is called, and finally the pointer to ESP is restored to where it was before the call. This is basically the compiler in compiling "Func3 (1, 2);" The code that is secretly implemented in the background when the statement is So for the variable parameter function call we need to implement, the stack and cleanup work is a bit more complicated, because we can't predict the number of parameters, so the stack of instructions (push) will not be fixed number, but must be implemented in a loop. The simple implementation code is as follows:

int Func5 (int inum)
{
int* p = new Int[inum];
Initialize P ...

__asm
{
mov ebx, DWORD ptr [P];//Put P's first address (base) into ebx
mov ecx, DWORD ptr [inum];//Put inum value into ECX, both as a loop control variable and as an offset value
mov edx, ECX///Put the value of ECX (Inum) into the edx, will be as the first parameter of FUNC1 pressure stack
Dec ecx//Descending ecx (P[inum-1] is the last parameter address)

LOOP1:

mov eax, DWORD ptr [ebx+ecx*4];//reverse-load the contents of the array p into the EAX, the offset value is ecx, descending by 4
push eax;//put eax pressure stack
Dec ecx//Descending ecx

JNS LOOP1//If ECX is not a negative value, jump to LOOP1:

Push edx//Put edx (inum) pressure stack, as the first parameter of FUNC1

Call Func1;//Calling Func1

mov ebx, DWORD ptr [inum];//Put inum value into ebx
SHL EBX, 2//left two bit, this is the size of the variable parameter
add ebx, 4;//EBX plus 4, this is the size of the first parameter
Add ESP, EBX//recovery stack pointer
}

Delete[] p;
}

This code is a little bit more complicated than the Func4 above, but it is clear: the process of cyclic compaction is LOOP1 to the code between "Jns LOOP1", and it is noteworthy that after calling the function Func1, you need to recalculate the position of the stack top pointer (ESP), which requires a new Inum, and try not to use both the EAX and edx registers, as they may be used to pass FUNC1 return values. For the return value of the function, if the returned data is less than 32 bits, it will be placed in the EAX register, if its size is greater than 32 bits less than 64 bits, then the edx save high 32 bits, eax save a low 32 bit. In other cases, the compiler will open a new memory in memory to store the return value, and then pass the memory pointer in the EAX.

This should be the end of the problem. However, in order to satisfy some cases that are not an int array type as arguments, we need to the function of the parameters of the stack way to understand: for the built-in data types for the parameters of the stack, with the 32-bit alignment of the way the stack, less than 32, to fill 32-bit (in fact, what does not matter, These bits are not used in the called function, but the push instruction requires 32 digits. Larger than 32 bits, the stack is pressed in order from highest to lowest. Variable pressure stacks that cannot be converted to built-in data types are much more complicated, as this involves the construction and destructor of these objects, which are not described in detail here. For the convenience of invocation, I have simply encapsulated it into a class:

Class Cvararg
{
Public:
 template <class t>
 void setarg (const t& targ)
  {
  if (sizeof (T) = sizeof (unsigned __int32))
  {
   unsigned __int32 Iarg = * (unsigned __int32*) &tArg;
   m_args.push_back (IARG);
  }
  else
  {
   int isize = (sizeof (t) & 0x03) = = 0)? sizeof (T) > > 2: (sizeof (T) >> 2) + 1;
   unsigned __int32* p = new unsigned __int32[isize];
   memcpy (p, &targ, sizeof (T));
   for (int i=0; i<isize; i++)
   {
    m_args.push _back (P[i]);
   }
   delete[] p;
  }
 }

Template <class t>
T Run (void* pfunc)
{
unsigned __int32 inum = M_args.size ();
unsigned __int32* p = &m_Args[iNum-1]; M_args[inum-1] is the last parameter address

__asm
{
mov ebx, DWORD ptr [P];//Add p to the address (the tail address of the argument list) into ebx
mov ecx, DWORD ptr [inum];//Put inum value into ecx as loop control variable
Dec ecx//Descending ecx

LOOP1:

mov eax, DWORD ptr [EBX];//reverse-loads the contents of the array p (ebx points) to eax
Sub ebx, 4;//decrease EBX content by 4 (ebx point forward one bit)
push eax;//put eax pressure stack
Dec ecx//Descending ecx

JNS LOOP1//If ECX is not a negative value, jump to LOOP1:

Call DWORD ptr [PFUNC];//Calling Func1

mov ebx, DWORD ptr [inum];//Put inum value into ebx
SHL EBX, 2//left two bit, this is the size of the variable parameter
Add ESP, EBX//recovery stack pointer
}
}

void Reset (void)
{
M_args.clear ();
}

Private
std::vector<unsigned __int32> M_args;
};

In this class, Setarg is responsible for transforming the various types of parameters that need to be passed into an array of int, storing it in a temporary "stack" M_args, in the Run function, using vectors to store the features of address continuity, combining the example code given above Func5 to complete the call. To be aware of two points, first: in the Run function, the function pointer of the actual called target function is defined as void*, because all pointers are the same in the Assembly, without distinguishing the type of pointer. (In fact, all of the pointer types in C + + are for the compiler to see, as long as you can cheat the compiler, then used to Baiwujinji, but if the error, it will be inconceivable; second: There is no return statement at the end of this function, nor does it change any eax after calling the Pfunc function. EdX value, so the return value of the Run function is actually equivalent to the return value of the Pfunc function. (In fact, for this does not have "return" statement of the function, I also feel a bit strange: the compiler not only passed the compiler, incredibly still a Warning are not, but good, save me to give it a forged return value).

The use of this class is also very convenient:
Cvararg va;
Va.  Setarg (1); Here you need to call Setarg in the correct order
Va. Setarg (2);
Va. Run<void> (FUNC1); If you need to return a value, use a similar t-t = Va. Run<t> (FUNC1); The statement calls

Of course, this class also has a lot of optimization and improvement space, especially the Setarg function, if interested, you can continue to study. But here's to remind you: because the assembly code portability is not strong, so if you need to copy the code here, you must fully test this part of the code, and as much as possible to cover all possible situations, such as various parameter types, various call/called functions, and so on. Otherwise, it is difficult to find out the problem.

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.