views:

889

answers:

8

To allow access to the Win32 API from a scripting language (written in C), I would like to write a function such as the following:

void Call(LPCSTR DllName, LPCSTR FunctionName, 
  LPSTR ReturnValue, USHORT ArgumentCount, LPSTR Arguments[])

which will call, generically, any Win32 API function.

(the LPSTR parameters are essentially being used as byte arrays - assume that they have been correctly sized to take the correct data type external to the function. Also I believe that some additional complexity is required to distinguish between pointer and non-pointer arguments but I'm ignoring that for the purposes of this question).

The problem I have is passing the arguments into the Win32 API functions. Because these are stdcall I can't use varargs so the implementation of 'Call' must know about the number of arguments in advance and hence it cannot be generic...

I think I can do this with assembly code (by looping over the arguments, pushing each to the stack) but is this possible in pure C?

Update: I've marked the 'No it is not possible' answer as accepted for now. I will of course change this if a C-based solution comes to light.

Update: ruby/dl looks like it may be implemented using a suitable mechanism. Any details on this would be appreciated.

+5  A: 

No, I don't think its possible to do with without writing some assembly. The reason is you need precise control over what is on the stack before you call the target function, and there's no real way to do that in pure C. It is, of course, simple to do in Assembly though.

Also, you're using PCSTR for all of these arguments, which is really just const char *. But since all of these args aren't strings, what you actually want to use for return value and for Arguments[] is void * or LPVOID. This is the type you should use when you don't know the true type of the arguments, rather than casting them to char *.

SoapBox
I'm using char* here as an array of bytes rather than strings. The assumption being that I make metadata available to the 'Call' function that describes the sizes of the actual types (i.e. number of bytes) which are required. In that sense the types *are* known - they are a known number of bytes.
Matthew Murdoch
In general you need more than the number of bytes to make a call, since the passing convention may be different for different types of data (particularly for floating-point arguments). But I you may be right that win32 __stdcall just puts everything on the stack, I don't know that one in detail.
puetzk
+8  A: 

First things first: You cannot pass a type as a parameter in C. The only option you are left with is macros.

This scheme works with a little modification (array of void * for arguments), provided you are doing a LoadLibrary/GetProcAddress to call Win32 functions. Having a function name string otherwise will be of no use. In C, the only way you call a function is via its name (an identifier) which in most cases decays to a pointer to the function. You also have to take care of casting the return value.

My best bet:

// define a function type to be passed on to the next macro
#define Declare(ret, cc, fn_t, ...) typedef ret (cc *fn_t)(__VA_ARGS__)

// for the time being doesn't work with UNICODE turned on
#define Call(dll, fn, fn_t, ...) do {\
    HMODULE lib = LoadLibraryA(dll); \
    if (lib) { \
        fn_t pfn = (fn_t)GetProcAddress(lib, fn); \
        if (pfn) { \
            (pfn)(__VA_ARGS__); \
        } \
        FreeLibrary(lib); \
    } \
    } while(0)

int main() {
    Declare(int, __stdcall, MessageBoxProc, HWND, LPCSTR, LPCSTR, UINT);

    Call("user32.dll", "MessageBoxA", MessageBoxProc, 
          NULL, ((LPCSTR)"?"), ((LPCSTR)"Details"), 
          (MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2));

    return 0;
}
dirkgently
So are you saying that it *is* possible to do this in C - the issue being the actual call to the Win32 function (which obviously I have no control over) rather than the prototype of the 'Call' function in my example?
Matthew Murdoch
Doesn't the use of __VA_ARGS__ here assume that the call is cdecl rather than stdcall (and hence the arguments will be pushed onto the stack in the wrong order)?
Matthew Murdoch
__VA_ARGS__ is a variadic macro. This ceases to exist after the pre-processing step. This is not the same as the va_args for variadic functions. Note also that I am passing the type of the function as a macro parameter.
dirkgently
OK, great! I'm having problems compiling this (Visual Studio 2003). I'm getting: "'.' : unexpected in macro formal parameter list" and "'__VA_ARGS__': undeclared identifier" - even if I #include <stdarg.h> - any suggestions?
Matthew Murdoch
My ideal solution would also not require the hard-coded typedef - it would be great if I could supply this information at runtime. Is this possible?
Matthew Murdoch
I used VS2005 to compile the sample provided above. Off the top of my head -- VS2003 probably doesn't support variadic macros. Will need to look up MSDN though.
dirkgently
'the hard-coded typedef' -- another variadic macro. Let me know if you have issues creating that -- will touch base later.
dirkgently
From MSDN (http://msdn.microsoft.com/en-us/library/ms177415(VS.80).aspx): Support for variadic macros was introduced in Visual C++ 2005 - this may not be a huge problem, although it does prevent me from testing this in the short term.
Matthew Murdoch
If you could add details for using variadic macros for 'the hard-coded typedef' I'll upvote and accept this answer. Thanks.
Matthew Murdoch
I thought this'd be easy ;-) I've updated but I am wondering if this is what you have in mind.
dirkgently
+1, but the sticking point for me is the need to 'Declare' the function signature metadata within the C code. I want to be able to supply it at runtime (say read from a text file). My intent being to add the ability to arbitrarily call into the Win32 API from a scripting language (implemented in C).
Matthew Murdoch
You have changed the entire problem description with a single line. You need something much more powerful -- take a look at m4.
dirkgently
Huge apologies for not being more specific from the outset. I will take a look at m4. Thanks.
Matthew Murdoch
The wrappers I have provided are handy shortcuts when you don't want to type the same thing over and over again, but know which function(s) to use. But, your problem is a different one -- you want templatized code generation, correct?
dirkgently
I'd rather not have code generation. I'd like a text file (say) which contains the details of the Win32 function signatures available for calling. This file would be read at runtime by a generic 'engine' to allow param validation and marshalling of args. It's the 'engine' part I'm struggling with...
Matthew Murdoch
Does it have to be in C? Use a script to generate the C code.
dirkgently
Yes (or C++, but I wouldn't imagine that changes much?). I'm thinking along the lines of ruby/dl (http://www.jbrowse.com/text/rdl_en.html) which looks like it must have solved this exact problem already...
Matthew Murdoch
C++ would allow some templates, but then again, you have to read in a file. So there goes C++.
dirkgently
A: 

Having a function like that sounds like a bad idea, but you can try this:

int Call(LPCSTR DllName, LPCSTR FunctionName, 
  USHORT ArgumentCount, int args[])
{
   void STDCALL (*foobar)()=lookupDLL(...);

   switch(ArgumentCount) {
        /* Note: If these give some compiler errors, you need to cast
           each one to a func ptr type with suitable number of arguments. */
      case 0: return foobar();
      case 1: return foobar(args[0]);
      ...
   }
}

On a 32-bit system, nearly all values fit into a 32-bit word and shorter values are pushed onto stack as 32-bit words for function call arguments, so you should be able to call virtually all Win32 API functions this way, just cast the arguments to int and the return value from int to the appropriate types.

TrayMan
For a generic implementation I don't really want to have switch (or if-else) code for different numbers of arguments, although I agree that there is a practical limit to the number of arguments I would need to cater for.
Matthew Murdoch
You also need to call cdecl functions...
RBerteig
A: 

I'm not sure if it will be of interest to you, but an option would be to shell out to RunDll32.exe and have it execute the function call for you. RunDll32 has some limitations and I don't believe you can access the return value whatsoever but if you form the command line arguments properly it will call the function.

Here's a link

Rich
Thanks for the link but it suggests that RunDll32 explicitly disallows calling any Win32 API functions...
Matthew Murdoch
A: 

First, you should add the size of each argument as an extra parameter. Otherwise, you need to divine the size of each parameter for each function to push onto the stack, which is possible for WinXX functions since they have to be compatible with the parameters they are documented, but tedious.

Secondly, there isn't a "pure C" way to call a function without knowing the arguments except for a varargs function, and there is no constraint on the calling convention used by a function in a .DLL.

Actually, the second part is more important than the first.

In theory, you could set up a preprocessor macro/#include structure to generate all combinations of parameter types up to, say, 11 parameters, but that implies that you know ahead of time which types will be passed through you function Call. Which is kind of crazy if you ask me.

Although, if you really wanted to do this unsafely, you could pass down the C++ mangled name and use UnDecorateSymbolName to extract the types of the parameters. However, that won't work for functions exported with C linkage.

MSN
+1  A: 

A lot of Win32 APIs take pointers to structs with specific layouts. Of these, a large subset follow a common pattern where the first DWORD has to be initialized to have the size of the struct before it is called. Sometimes they require a block of memory to be passed, into which they will write a struct, and the memory block must be of a size that is determined by first calling the same API with a NULL pointer and reading the return value to discover the correct size. Some APIs allocate a struct and return a pointer to it, such that the pointer must be deallocated with a second call.

I wouldn't be that surprised if the set of APIs that can be usefully called in one shot, with individual arguments convertable from a simple string representation, is quite small.

To make this idea generally applicable, we would have to go to quite an extreme:

typedef void DynamicFunction(size_t argumentCount, const wchar_t *arguments[],
                             size_t maxReturnValueSize, wchar_t *returnValue);

DynamicFunction *GenerateDynamicFunction(const wchar_t *code);

You would pass a simple snippet of code to GenerateDynamicFunction, and it would wrap that code in some standard boilerplate and then invoke a C compiler/linker to make a DLL from it (there are quite a few free options available), containing the function. It would then LoadLibrary that DLL and use GetProcAddress to find the function, and then return it. This would be expensive, but you would do it once and cache the resulting DynamicFunctionPtr for repeated use. You could do this dynamically by keeping pointers in a hashtable, keyed by the code snippets themselves.

The boilerplate might be:

#include <windows.h> 
// and anything else that might be handy

void DynamicFunctionWrapper(size_t argumentCount, const wchar_t *arguments[],
                             size_t maxReturnValueSize, wchar_t *returnValue)
{
    // --- insert code snipped here
}

So an example usage of this system would be:

DynamicFunction *getUserName = GenerateDynamicFunction(
    "GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))");

wchar_t userName[100];
getUserName(0, NULL, sizeof(userName) / sizeof(wchar_t), userName);

You could enhance this by making GenerateDynamicFunction accept the argument count, so it could generate a check at the start of the wrapper that the correct number of arguments has been passed. And if you put a hashtable in there to cache the functions for each encountered codesnippet, you could get close to your original example. The Call function would take a code snippet instead of just an API name, but would otherwise be the same. It would look up the code snippet in the hashtable, and if not present, it would call GenerateDynamicFunction and store the result in the hashtable for next time. It would then perform the call on the function. Example usage:

wchar_t userName[100];

Call("GetUserNameW(returnValue, (LPDWORD)(&maxReturnValueSize))",
     0, NULL, sizeof(userName) / sizeof(wchar_t), userName);

Of course there wouldn't be much point doing any of this unless the idea was to open up some kind of general security hole. e.g. to expose Call as a webservice. The security implications exist for your original idea, but are less apparent simply because the original approach you suggested wouldn't be that effective. The more generally powerful we make it, the more of a security problem it would be.

Update based on comments:

The .NET framework has a feature called p/invoke, which exists precisely to solve your problem. So if you are doing this as a project to learn about stuff, you could look at p/invoke to get an idea of how complex it is. You could possibly target the .NET framework with your scripting language - instead of interpreting scripts in real time, or compiling them to your own bytecode, you could compile them to IL. Or you could host an existing scripting language from the many already available on .NET.

Daniel Earwicker
+1, but my intent is to add the ability to arbitrarily call into the Win32 API from a scripting language implemented in C (rather than open up a security hole!). I'd rather the implementation was not dependent on a C compiler/linker if possible.
Matthew Murdoch
Thanks for the update (unfortunately I can't upvote you again!). I am looking for a C-based solution, ideally but the .NET option is food for thought.
Matthew Murdoch
The technique does have use cases that are not intended to be security holes. One is to allow script authors to adapt foreign code or system APIs to their scripting language without needing to be able to compile.
RBerteig
+3  A: 

The other posts are right about the almost certain need for assembly or other non-standard tricks to actually make the call, not to mention all of the details of the actual calling conventions.

Windows DLLs use at least two distinct calling conventions for functions: stdcall and cdecl. You would need to handle both, and might even need to figure out which to use.

One way to deal with this is to use an existing library to encapsulate many of the details. Amazingly, there is one: libffi. An example of its use in a scripting environment is the implementation of Lua Alien, a Lua module that allows interfaces to arbitrary DLLs to be created in pure Lua aside from Alien itself.

RBerteig
+1  A: 

You could try something like this - it works well for win32 API functions:

int CallFunction(int functionPtr, int* stack, int size)
{
    if(!stack && size > 0)
        return 0;
    for(int i = 0; i < size; i++) {
        int v = *stack;
        __asm {
            push v
        }
        stack++;
    }
    int r;
    FARPROC fp = (FARPROC) functionPtr;
    __asm {
        call fp
        mov dword ptr[r], eax
    }
    return r;
}

The parameters in the "stack" argument should be in reverse order (as this is the order they are pushed onto the stack).

Peter Smith