6
votes

The problem I have is that I want to create a generic command line application that can be used to load a library DLL and then call a function in the library DLL. The function name is specified on the command line with the arguments also provided on the utility command line.

I can access the external function from a DLL dynamically loaded using the LoadLibrary() function. Once the library is loaded I can obtain a pointer to the function using GetProcAddress() I want to call the function with the arguments specified on the command line.

Can I pass a void-pointer-list to the function-pointer which I got returned by the LoadLibrary() function similar to the example below?

To keep the example code simple, I deleted the error-checking. Is there a way to get something like this working:

    //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 mention necessary information, please let me know. Thanks in advance

2
Calling convention could eventually give you grief. The actual function expectations how you're calling it are an obvious assumption. And "... with passed arguments.", noting the plurality of that statement, is interesting. You're passing one. If you need more than that (i.e. you're expecting this to magically send argv[3]...argv[argc-1] as function arguments), this won't do it, and doing it right gets complicated quickly.WhozCraig
It is not clear what you mean by if I could pass a void-pointer-list to the function-pointer. If the function you are calling is defined as ... MyFunction(void *), then yes, you can call it like this, otherwise you would not be able to. Also, make sure it is labeled as declspec(stdcall).seva titov
I just looked for a way to get a command-line tool to call e.g. kernel32.dll and any of its function and pass arguments to it. I was hoping - as WhozCraig said - for a magic way to find the argument list from an imported functionh4x0r
@WhozCraig thanks for your comment. "you're expecting this to magically send argv[3]...argv[argc-1] as function arguments), this won't do it" is exactly what I was looking for (or at least a workaround for this).h4x0r

2 Answers

4
votes

You need to cast the function pointer returned by LoadLibrary to one with the right argument types before calling it. One way to manage it is to have a number call-adaptor functions that do the right thing for every possible function type you might want to 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 you got 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 the type of the function you're getting the pointer for is and call the appropriate adaptor function. Which generally means making a table of function name/adaptors and doing a lookup in it.

The related problem is that there's no function analogous to GetProcAddress that will tell you the argument types for a function in the library -- that information simply isn't stored anywhere accessable in the dll.

2
votes

A library DLL contains the object code for the functions that are part of the library along with some additional information to allow the DLL to be usable.

However a library DLL does not contain the actual type information needed to determine the specific argument list and types for the functions contained in the library DLL. The main information in a library DLL is: (1) a list of the functions that the DLL exports along with the address information that will connect a call of a function to the actual function binary code and (2) a list of any required DLLs that the functions in the library DLL use.

You can actually open a library DLL in a text editor, I suggest a small one, and scan through the arcane symbols 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 a library DLL contains the bare minimum information needed to (1) find a particular function in the library DLL so that it can be invoked and (2) a list of other needed DLLs that the functions in the library DLL depend on.

This is different from a COM object which normally does have type information in order to support the ability to do what is basically reflection and explore the COM object's services and how those services are accessed. You can do this with Visual Studio and other IDEs which generate a list of COM objects installed and allow you to load a COM object and explore it. Visual Studio also has a tool that will generate the source code files that provide the stubs and include file for accessing the services and methods of a COM object.

However a library DLL is different from a COM object and all the additional information provided with a COM object is not available from a library DLL. Instead a library DLL package is normally made up of (1) the library DLL itself, (2) a .lib file that contains the linkage information for the library DLL along with the stubs and functionality to satisfy the linker when building your application which uses the library DLL, and (3) an include file with the function prototypes of the functions in the library DLL.

So you create your application by calling the functions which reside in the library DLL but using the type information from the include file and linking with the stubs of the associated .lib file. This procedure allows Visual Studio to automate much of the work required to use a library DLL.

Or you can hand code the LoadLibrary() and the building of a table of the functions in the library DLL using GetProcAddress(). By doing hand coding all you really need are the function prototypes of the functions in the library DLL which you then can type in yourself and the library DLL itself. You are in effect doing the work by hand that the Visual Studio compiler does for you if you are using the .lib library stubs and include file.

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

  • the name of the function to be called as a text string on the command line
  • the list of the arguments to be used as a series of text strings on the command line
  • an additional parameter that describes the function prototype

This is similar to how functions in the C and C++ runtime which accept variable argument lists with unknown parameter types work. For instance the printf() function which prints a list of argument values has a format string followed by the arguments to be printed. The printf() function uses the format string to determine the types of the various arguments, how many arguments to expect, and what kinds of value transformations to do.

So if your utility had a command line something like the following:

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

And the library DLL had a function whose prototype looked like:

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

then the utility would dynamically generate the function call by transforming the character strings of the command line into the actual types needed for the function to be called.

This would work for simple functions that take Plain Old Data types however more complicated types such as struct type argument would require more work as you would need some kind of a description of the struct along with some kind of argument description perhaps similar to JSON.

Appendix I: A simple example

The following is the source code for a Visual Studio Windows console application that I ran in the debugger. The command arguments in the Properties was pif.dll PifLogAbort which caused a library DLL from another project, pif.dll, to be loaded and then the function PifLogAbort() in that library to be invoked.

NOTE: The following example depends on a stack based argument passing convention as is used with most x86 32 bit compilers. Most compilers also allow for a calling convention to be specified other than stack based argument passing such as the __fastcall modifier of Visual Studio. Also as pointed out in the comments, the default for x64 and 64 bit Visual Studio is to use the __fastcall convention by default so that function arguments are passed in registers and not on the stack. See Overview of x64 Calling Conventions in the Microsoft MSDN. See as well the comments and discussion in How are variable arguments implemented in gcc? .

Notice how the argument list to the function PifLogAbort() is built as a structure that contains an array. The argument values are put into the array of a variable of the struct and then the function is called passing the entire struct by value. What this does is to push a copy of the array of parameters onto the stack and then calls the function. The PifLogAbort() function sees the stack based on its argument list and processes the array elements as individual 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 hurdles that must be overcome. You will need to know how to translate the various parameter types into the proper alignment in a memory area which is then pushed onto the stack.

The interface to this example function is remarkably homogeneous in that most of the arguments are unsigned char pointers with the exception of the last which is an int. With a 32 bit executable all four of these variable types have the same length in bytes. With a more varied list of types in the argument list you will need to have an understanding as to how your compiler aligns parameters when it is pushing the arguments onto the stack before doing the call.

Appendix II: Extending the simple example

Another possibility is to have a set of helper functions along with a different version of the struct. The struct provides a memory area to create a copy of the necessary stack and the help functions are used to build the copy.

So the struct and its helper functions may look like the following.

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 the struct and helper functions would be used to build the argument list like the following after which the function from the library DLL is invoked with the copy of the stack provided:

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);