Dynamic call of DLL functions at runtime without function declaration

Source: Internet
Author: User

Source: http://www.graphixer.com.cn/ShowWorks.asp? Type = 1 & id = 77

 

 

We all know that there are two DLL call Methods: dynamic call and static call. Static calls tell the compiler that I need a DLL, define all the function declarations to be used, and call these functions at runtime. This usage is similar to that of static libraries. Dynamic calling is to use loadlibrary to load a DLL to the runtime environment, and then get the specific function pointer through getprocaddress and call it.

However, dynamic calling still requires that the prototype of the function be determined during compilation, because in C ++, only function pointers can be used to call the function. However, in some cases, the parameter table and return type of the function in the DLL cannot be determined during compilation, and can only be obtained in some way at runtime. How can we call the DLL function at this time? This is the problem to be solved in this article.

First, under what circumstances can this problem be encountered. For example, if I am working on a script language, I need to allow the script to call the DLL function at runtime, but the main program of the DLL function to be called by the script is unknown, however, before the script is called, it will first declare the DLL function to be called, the Declaration includes the return type of the function, the parameter table, the DLL file name of the function, the name of the function in the DLL, and the call conventions of the function. With this information, how does the main program respond to the requirements of the script to call the relevant DLL? Traditional methods require the compiler to know the prototype of the function to be called, which is obviously not acceptable. Then, we need to dynamically pass these parameters to the function at runtime and then call it. This is beyond the scope of C ++, so it needs to be implemented using Embedded Assembly.

By using assembly, we bypass the C ++ parameter check to directly call a function address. Of course, we need to ensure that the number of parameters passed is the same as the number of parameters of the called function.

Before writing code, you need to understand the function call conventions of C ++. C ++ allows the following call Conventions: __cdecl, _ stdcall, _ fastcal ,__ thiscall and _ clrcall. _ Thiscall is used to call class member functions, __clrcall is used to host C ++, and _ fastcall is used to pass Parameters in registers. _ Thiscall is used to access object member functions. This is not part of our discussion. _ Fastcall: Because parameters are transmitted through registers, it must be implemented by the function caller and the called function. Because we can only control the function caller, the behavior of _ fastcall cannot be determined, therefore, it does not fall into the scope of our discussion. In fact, _ fastcall is rarely used in the program and will not appear in the DLL export function. _ Clrcall is used for. net, which is not included in our discussion. Therefore, we need to care about _ cdecl and _ stdcall. _ Cdecl is the default call Convention of C/C ++, that is, when a function caller calls a function, all the parameters of the function are pushed to the stack in sequence from right to left, then call the function. Finally, the function caller is responsible for popping up all parameters in the stack. The difference between _ stdcall and _ cdecl is that _ stdcall pops up the parameter stack by the called function.

Therefore, the assembly code that calls the _ cdecl function should be in the following form:

push ParamNpush ParamN-1...push Param2push Param1call FuncPtrpop EAXpop EAX...pop EAX

When calling the _ stdcall function, all the pop commands that follow should be omitted.

The problem to be solved now is how to pass various types of parameters? How can I obtain different types of return values of a function? First, you must understand the push command. The push command can only push 4 bytes of data into the stack at a time. If you want to transfer 8 bytes of data, such as double and int64, it must be divided into two parts into the stack. The low byte is in the front and the high byte is in the back. That is, if

union{    double d     struct    {        int HighPart, LowPart;    }Parts;} data

To press d into the stack, the compilation should be written as follows:

push data.LowPartpush data.HighPart

The same is true for int64. Note that in assembly, no matter what type of data you operate on, you only care about the number of bytes of data to be transmitted.

For char, wchar_t, a data structure smaller than 4 bytes, should be converted to a 4-byte integer for transmission. For example:

wchar_t wparam;int param = wparam;__asm{    push param}

Convert wparam to 4 bytes and press it into the stack.

Then the return value is returned. Different types of function return values are stored in different places. Integer return values, such as char, wchar_t, Int, and unsign int, are stored in the eax register, and int64 is stored in the edX: eax register pair, where edX stores high bytes, eax stores low-level bytes. The Return Value of the floating point type is stored at the top of the FPU stack. You must use the fstp command to obtain the data at the top of the FPU stack.

So when we call the function, if the function type is integer, we get the return value through the following code:

int HighPart, LowPart;__asm{     mov int ptr[HighPart], EDX     mov int ptr[LowPart], EAX}

Int in the Assembly is a type indicator, indicating that the Register value is placed in the memory space with PTR [highpart] as the starting address and length sizeof (INT. PTR is equivalent to the "&" Operator in C ++.

In this way, in the above Code, if the return value of the function is a signed/unsigned integer smaller than or equal to 4 bytes, the integer is the value of the lowpart variable. If int64 is used, the corresponding int64 value can be obtained by merging highpart and lowpart:

union{    __int64 Value;    struct{ int High, Low} Parts;} Data;Data.Parts.High = HighPart;Data.Parts.Low = LowPart;

In this way, Data. value is the value we want.

Now let's look at the floating point type. To obtain the floating point return value, you must use the fstp command to roll the top data of the FPU stack to a variable. For the double type, the following code obtains the data:

double result;__asm{     FSTP [result]}

Similarly, if it is float, the following code can obtain its return value:

float result;__asm{    FSTP [result]}

The version is exactly the same as that of double.

All the things are finished. Now let's look at the sample code below.

Hmodule Lib = loadlibraryw (L "user32.dll ");
Farproc func = getprocaddress (Lib, "messageboxw ");
Wchar_t * MSG = l "test MSG ";
Wchar_t * Title = l "test title ";
Int result;
_ ASM
{
Push 0
Push title
Push msg
Push 0
Call func
MoV int PTR [Result], eax
}

 

The above code calls the messageboxw function. The prototype of messageboxw is: int _ stdcall messageboxw (INT hwnd, const wchar_t * MSG, const wchar_t * Title, int msgboxtype ). The Code does not need to be explained.

The variables in C ++ can be directly written in the Assembly. However, in assembly, only local variables in the function can be referenced, and member variables cannot be referenced. Otherwise, the pointer must be used. In addition, the variables referenced in the Assembly must be of the original type, that is, data of the struct, union, and class types cannot be used.

Now let's take a look at how to mix C ++ code and assembly code to implement a universal dynamic DLL caller. Note that all Assembly commands that use registers should be written in the same _ ASM block as the commands that store the desired data to the registers. If it is written in different _ ASM blocks, even if it is connected together, the correct register data cannot be obtained. Just make sure that this is done, __how to mix the ASM block and C ++ code. Not to mention, paste the Code directly.

Code: dllcall. h

# Ifndef gx_dll_runtime_call_h # define gx_dll_runtime_call_h # include "gxlibbasic. H "# include" windows. H "// gxdllvariable: Class gxdllvariable: Public gxobject {public: Enum gxvariabletype {gxatvoid, gxatint, gxatchar, gxatwchar, gxatdouble, gxatfloat, gxatint64}; gxvariabletype type; Union {int intval; char charval; wchar_t wcharval; double doubleval; float floatval ;__ int64 int64val;} data; gxdllva Riable (); gxdllvariable (INT Val); gxdllvariable (float Val); gxdllvariable (double Val); gxdllvariable (_ int64 Val); gxdllvariable (char Val ); gxdllvariable (wchar_t Val) ;}; class gxdllfunction: Public gxobject {public: Enum gxcallconvention {signature, signature}; private: farproc ffuncptr; public: gxdllfunction (hmodule Lib, gxstring funcname ); ~ Gxdllfunction (); Public: gxdllvariable invoke (gxarray & ARGs, gxdllvariable: gxvariabletype returntype, gxcallconvention Conv = gxccstdcall);}; # endif

Code: dllcall. cpp

 

# Include "dllcall. H" gxdllfunction: gxdllfunction (hmodule Lib, gxstring funcname) {ffuncptr = getprocaddress (Lib, funcname. tombstring (); If (! Ffuncptr) Throw gxdlllinkexception (funcname);} gxdllvariable gxdllfunction: invoke (gxarray & ARGs, gxdllvariable: gxvariabletype returntype, gxcallconvention Conv) {// structure used to store 8-byte data: Union longtype {double doubleval ;__ int64 intval; struct {int head, tail;} parts ;}; // use the stdcall/cdecl function call convention, the parameter from right to left pressure stack for (INT I = args. count ()-1; I> = 0; I --) {gxdllvariable Var = ARGs [I]; longtype L; // put single-byte data in a 4-Byte variable, so that the int TMP = var. data. charval; // press different types of data into the stack switch (ARGs [I]. type) {Case gxdllvariable: gxatchar: // single-byte integer _ ASM {push TMP}; break; Case gxdllvariable: gxatdouble: // 8-byte floating point // The 8-byte data is pushed into the stack in two parts, and the stack is first written into the stack at a low position. doubleval = var. data. doubleval ;__ ASM {push L. parts. tailpush L. parts. head} break; Case gxdllvariable: gxatfloat: // 4-byte floating point _ ASM {push var. data. floatval;} break; Case gxdllvariable: gxatint: // 32-bit integer _ ASM push var. data. intval; break; Case gxdllvariable: gxatwchar: // 16-bit integer _ ASM push var. data. wcharval; break; Case gxdllvariable: gxatint64: // 64-bit integer L. intval = var. data. int64val ;__ ASM {push L. parts. tailpush L. parts. head} break; Case gxdllvariable: gxatvoid: // For function parameters, the void type is invalid throw l "cannot pass void as an argument. "; break ;}// Embedded Assembly can only access the internal variables of the function. Therefore, copy the function pointer to farproc fptr = ffuncptr; // call the function and obtain the information stored in EDX, the Return Value of the integer function in eax is longtype ltval; int itval, ihval ;__ ASM {call fptrmov int PTR [ihval], edxmov int PTR [itval], eax} ltval. parts. head = ihval; // only the ltval of int64 type is used. parts. tail = itval; // sort the function return values into gxdllvaraiable structure gxdllvariable retval; retval. type = returntype; Switch (returntype) {Case gxdllvariable: gxatchar: retval. data. charval = ltval. parts. tail; break; Case gxdllvariable: gxatdouble: // For floating-point return values, read _ ASM fstp [retval. data. doubleval]; break; Case gxdllvariable: gxatfloat: // For floating point return values, read _ ASM fstp [retval. data. floatval]; break; Case gxdllvariable: gxatint: retval. data. intval = ltval. parts. tail; break; Case gxdllvariable: gxatwchar: retval. data. wcharval = ltval. parts. tail; break; Case gxdllvariable: gxatint64: retval. data. int64val = ltval. intval; break; Case gxdllvariable: gxatvoid: break ;}
// Use the C/C ++ default call Convention. The caller needs to pop up the variable if (Conv = gxcccdecl) {for (INT I = 0; I

The following is an example of the gxdllfunction class.

Testdll. cpp used to compile the DLL:

double DoSomething(int a, double b, __int64 c, double * d){*d = b/2;return (double)(c+a);}

Note that you need to export this function in the def file to compile this DLL.

Main. CPP, the main program that calls this dll:

# Include "gxlibrary/dllcall. H"
# Include

Using namespace STD;

Void runtest ()
{
Double D = 0.0;
// Load the dynamic link library
Hmodule Lib = loadlibraryw (L "testdll ");

// Obtain the function from the dynamic link library
Gxdllfunction runtimefunction (Lib, l "dosomething ");

// Set the parameter type as gxdllvariable and put it in the array
Gxarray ARGs;
Args. Add (gxdllvariable (4); // parameter A, int type
Args. Add (gxdllvariable (6.4); // parameter B, double type
Args. Add (gxdllvariable (_ int64) 1 <32); // parameter C, int64 type
Args. Add (gxdllvariable (INT) (& D); // parameter D, double * type

// Call the function, convert the returned result of the function to int64, and put it in the result variable.
_ Int64 result = (_ int64) runtimefunction. Invoke
(
ARGs,
Gxdllvariable: gxatdouble,
Gxdllfunction: gxcccdecl
). Data. doubleval;

// Output result
Cout <"DLL function invoked! /Nreturn value :"
<Running result:

 

 

Click here to download the sample program and all the code (Visual C ++ 2008 ).

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.