C Pass arguments as void-pointer-list for the imported function from LoadLibrary ()

The problem is that I want to create a common command line application that can be used to load the DLL, and then call the function in the library DLL. The name of the function is indicated on the command line with the arguments specified on the command line of the utility.

I can access an external function from a dynamic DLL loading using the LoadLibrary() function. After loading the library, I can get a pointer to a function using GetProcAddress() I want to call a function with the arguments specified on the command line.

Can I pass a pointer to a pointer to a function that I received with a LoadLibrary() function similar to the one below?

To keep the sample code simple, I removed the error check. Is there a way to get something like this:

  // Somewhere in another dll
     int DoStuff (int a, int b)
     {
         return a + b;
     }
  int main (int argc, char ** argv)
     {
         void * retval;
         void * list = argv [3];
         HMODULE dll;
         void * (* generic_function) (void *);

         dll = LoadLibraryA (argv [1]);

         // argv [2] = "DoStuff"
         generic_function = GetProcAddress (dll, argv [2]);

         // argv [3] = 4, argv [4] = 7, argv [5] = NULL
         retval = generic_function (list);
     }

If I forgot to provide the necessary information, please let me know. thanks in advance

+6
source share
2 answers

Before calling, you need to point the function pointer returned by LoadLibrary to one with the correct argument types. One way to manage it is to have dialing functions for calls that do the right thing for all the possible types of functions that you might call:

 void Call_II(void (*fn_)(), char **args) { void (*fn)(int, int) = (void (*)(int, int))fn_; fn(atoi(args[0]), atoi(args[1])); } void Call_IS(void (*fn_)(), char **args) { void (*fn)(int, char *) = (void (*)(int, char *))fn_; fn(atoi(args[0]), args[1]); } ...various more functions 

Then you take the pointer obtained from GetProcAddress and the additional arguments and pass them to the correct Call_X function:

 void* (*generic_function)(); dll = LoadLibraryA(argv[1]); //argv[2] = "DoStuff" generic_function = GetProcAddress(dll, argv[2]); //argv[3] = 4, argv[4] = 7, argv[5] = NULL Call_II(generic_function, &argv[3]); 

The problem is that you need to know what type of function you get for the pointer, and call the corresponding adapter function. What usually means creating a table of names / function adapters and searching in it.

A related problem is that there is no function similar to GetProcAddress that tells you the types of arguments for a function in the library โ€” this information is simply not stored anywhere in the DLL.

+4
source

The DLL contains object code for functions that are part of the library, as well as some additional information that allows you to use the DLL.

However, the DLL does not contain the actual type information needed to define a specific list of arguments and types for the functions contained in the library DLL. The main information in the library DLL: (1) a list of functions that the DLL exports along with address information that will connect the function call to the actual binary code of the function; and (2) a list of any necessary DLLs that the functions of the DLL are used.

In fact, you can open the library DLL in a text editor, I suggest a small one and scan the secret characters of the binary code until you reach the section that contains the list of functions in the library DLL, as well as other required DLLs.

So, the DLL contains the minimum information necessary for (1) finding a specific function in the library DLL so that it can be called, and (2) a list of other necessary DLLs on which the functions of the DLL library depend.

This differs from a COM object, which typically has type information, to support the ability to do what basically reflects, and examine the services of COM objects and how they are accessed. You can do this with Visual Studio and other IDEs that generate a list of installed COM objects and allow you to load a COM object and examine it. Visual Studio also has a tool that will generate source code files that provide stubs and include a file to access the services and methods of the COM object.

However, the DLL is different from the COM object, and all the additional information provided by the COM object is not available from the DLL. Instead, a library DLL package usually consists of (1) the DLL itself, (2) a .lib file that contains information about linking the DLL along with stubs and functionality to satisfy the linker when creating an application that uses the library DLL and (3) Including a file with prototypes of function functions from the DLL.

So, you create your application by calling functions that are in the library DLL, but using the type information from the included file and associating them with the stubs of the associated .lib file. This procedure allows Visual Studio to automate most of the work required to use the DLL.

Or you can pass the code to LoadLibrary() and build a function table in the library DLL using GetProcAddress() . Performing manual encoding, all you really need are prototypes of functions in the DLL library, which you can then enter yourself and the library DLL itself. You actually do the manual work that the Visual Studio compiler does for you if you use the stubs of the .lib library and include the file.

If you know the actual function name and function prototype of the function in the library DLL, then what you could do is to have the following command line utility:

  • the name of the function to be called as a text string in the line command
  • list of arguments to be used as a series of text strings on the command line
  • optional parameter describing the function prototype

This is similar to how functions in the C and C ++ runtimes work, which accept lists of variable arguments with unknown parameter types. For example, the printf() function, which prints a list of argument values, has a format string followed by arguments to print. The printf() function uses a format string to determine the types of various arguments, the number of expected arguments, and how to convert the values.

So, if your utility has a command line something like this:

 dofunc "%s,%d,%s" func1 "name of " 3 " things" 

And the DLL library had a function, the prototype of which looked like this:

 void func1 (char *s1, int i, int j); 

then the utility will dynamically generate a function call, converting the character string of the command line to the actual types needed for the called function.

This will work for simple functions that take Plain Old Data, however, more complex types, such as the struct type argument, will require more work, as you will need some kind of struct description, as well as some description argument, possibly similar to JSON.

Appendix I: A Simple Example

Below is the source code for the Visual Studio Windows console application that I ran in the debugger. The command arguments in the properties were pif.dll PifLogAbort , which caused the DLL to be loaded from another pif.dll project, and then the PifLogAbort() function in that library, which should be called.

NOTE. . The following example depends on a stack-based argument passing agreement, which is used with most 32-bit x86 compilers. Most compilers also allow you to specify a calling convention other than passing arguments based on the stack, such as the __fastcall Visual Studio modifier. Also, as pointed out in the comments, the default for the 64-bit version of the 64-bit version of Visual Studio is to use the __fastcall by default, so the function arguments are passed in registers, not on the stack. See an overview of x64 conventions on Microsoft MSDN. See Also comments and discussion in How variable arguments are implemented in gcc? .

Notice how the argument list of the PifLogAbort() function is constructed as a structure containing an array. The values โ€‹โ€‹of the arguments are placed into the array of the struct variable, and then the function is called by passing the entire value of the struct by value. This is done in order to insert a copy of the parameter array onto the stack and then call the function. The PifLogAbort() function sees the stack by its list of arguments and treats the elements of the array as separate arguments or parameters.

 // dllfunctest.cpp : Defines the entry point for the console application. // #include "stdafx.h" typedef struct { UCHAR *myList[4]; } sarglist; typedef void ((*libfunc) (sarglist q)); /* * do a load library to a DLL and then execute a function in it. * * dll name.dll "funcname" */ int _tmain(int argc, _TCHAR* argv[]) { HMODULE dll = LoadLibrary(argv[1]); if (dll == NULL) return 1; // convert the command line argument for the function name, argv[2] from // a TCHAR to a standard CHAR string which is what GetProcAddress() requires. char funcname[256] = {0}; for (int i = 0; i < 255 && argv[2][i]; i++) { funcname[i] = argv[2][i]; } libfunc generic_function = (libfunc) GetProcAddress(dll, funcname); if (generic_function == NULL) return 2; // build the argument list for the function and then call the function. // function prototype for PifLogAbort() function exported from the library DLL // is as follows: // VOID PIFENTRY PifLogAbort(UCHAR *lpCondition, UCHAR *lpFilename, UCHAR *lpFunctionname, ULONG ulLineNo); sarglist xx = {{(UCHAR *)"xx1", (UCHAR *)"xx2", (UCHAR *)"xx3", (UCHAR *)1245}}; generic_function(xx); return 0; } 

This simple example illustrates some of the technical barriers that need to be overcome. You will need to know how to translate the various types of parameters into the correct alignment in the memory area, which is then pushed onto the stack.

The function interface of this example is extremely homogeneous, since most arguments are unsigned char pointers, with the exception of the latter, which is int . With 32-bit executable, all four of these types of variables are the same length in bytes. With a more diverse list of types in the argument list, you will need to understand how your compiler aligns the parameters when it pushes the arguments onto the stack before making the call.

Appendix II: Extending a Simple Example

Another possibility is to have a set of helper functions along with another version of struct . struct provides a memory area for creating a copy of the desired stack, and auxiliary functions are used to create a copy.

So, struct and its helper functions may look as follows.

 typedef struct { UCHAR myList[128]; } sarglist2; typedef struct { int i; sarglist2 arglist; } sarglistlist; typedef void ((*libfunc2) (sarglist2 q)); void pushInt (sarglistlist *p, int iVal) { *(int *)(p->arglist.myList + p->i) = iVal; p->i += sizeof(int); } void pushChar (sarglistlist *p, unsigned char cVal) { *(unsigned char *)(p->arglist.myList + p->i) = cVal; p->i += sizeof(unsigned char); } void pushVoidPtr (sarglistlist *p, void * pVal) { *(void * *)(p->arglist.myList + p->i) = pVal; p->i += sizeof(void *); } 

And then struct and helper functions will be used to create an argument list similar to the following, after which the function from the DLL is called with a copy of the provided stack:

 sarglistlist xx2 = {0}; pushVoidPtr (&xx2, "xx1"); pushVoidPtr (&xx2, "xx2"); pushVoidPtr (&xx2, "xx3"); pushInt (&xx2, 12345); libfunc2 generic_function2 = (libfunc2) GetProcAddress(dll, funcname); generic_function2(xx2.arglist); 
+2
source

Source: https://habr.com/ru/post/983675/


All Articles