如何编写用于调用Win32函数的通用C函数?

时间:2009-03-09 10:50:35

标签: c winapi interop libffi rubydl

为了允许从脚本语言(用C编写)访问Win32 API,我想编写如下函数:

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

一般会调用任何Win32 API函数。

(LPSTR参数基本上用作字节数组 - 假设它们的大小正确,以便在函数外部采用正确的数据类型。另外我认为需要一些额外的复杂性来区分指针和非指针争论,但我忽略了这个问题的目的)。

我遇到的问题是将参数传递给Win32 API函数。因为这些是stdcall我不能使用varargs所以'Call'的实现必须提前知道参数的数量,因此它不能是通用的......

我想我可以使用汇编代码(通过循环遍历参数,将每个参数推送到堆栈)来实现这一点但是在纯C中这是可能的吗?

更新:我已经标记了现在已接受的“不可能”答案。如果基于C的解决方案曝光,我当然会改变它。

更新: ruby/dl看起来可能是使用合适的机制实现的。任何细节都将不胜感激。

8 个答案:

答案 0 :(得分:8)

首先要做的事情是:你不能在C中传递一个类型作为参数。你剩下的唯一选择是宏。

如果您正在执行LoadLibrary/GetProcAddress调用Win32函数,则此方案稍作修改(参数的void *数组)。否则有一个函数名字符串是没用的。在C中,调用函数的唯一方法是通过其名称(标识符),在大多数情况下衰减到指向函数的指针。您还必须注意转换返回值。

我最好的选择:

// 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;
}

答案 1 :(得分:5)

不,如果不写一些装配,我认为不可能。原因是你需要在调用目标函数之前精确控制堆栈上的内容,并且在纯C中没有真正的方法可以做到这一点。当然,在汇编中这很简单。

此外,您正在将PCSTR用于所有这些参数,这实际上只是const char *。但由于所有这些args都不是字符串,因此您实际想要用于返回值和Arguments []的是void *LPVOID。当您不知道参数的真实类型时,应该使用此类型,而不是将它们转换为char *

答案 2 :(得分:2)

其他帖子是关于几乎确定需要汇编或其他非标准技巧来实际调用,而不是提及实际调用约定的所有细节。

Windows DLL对函数使用至少两个不同的调用约定:stdcallcdecl。你需要处理这两个问题,甚至可能需要弄清楚要使用哪个。

解决此问题的一种方法是使用现有库来封装许多细节。令人惊讶的是,有一个:libffi。它在脚本环境中使用的一个例子是Lua Alien的实现,Lua模块允许除了Alien本身之外,在纯Lua中创建任意DLL的接口。

答案 3 :(得分:1)

许多Win32 API都指向具有特定布局的结构。其中,一个大的子集遵循一个共同的模式,其中第一个DWORD必须在被调用之前被初始化为具有结构的大小。有时它们需要传递一块内存,它们将在其中写入结构,并且内存块的大小必须通过首先使用NULL指针调用相同的API并读取返回值以发现正确来确定尺寸。一些API分配一个结构并返回一个指向它的指针,这样就必须通过第二次调用来释放指针。

如果可以一次性调用的API集合(可以从简单的字符串表示形式转换的单个参数)非常小,我就不会感到惊讶了。

为了使这个想法普遍适用,我们必须走极端:

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

DynamicFunction *GenerateDynamicFunction(const wchar_t *code);

您可以将一段简单的代码传递给GenerateDynamicFunction,它会将该代码包装在一些标准样板中,然后调用C编译器/链接器从中创建一个DLL(有很多可用的免费选项),包含功能。然后它将LoadLibrary该DLL并使用GetProcAddress来查找该函数,然后将其返回。这将是昂贵的,但您可以执行一次并缓存生成的DynamicFunctionPtr以供重复使用。您可以通过将指针保留在哈希表中来动态地执行此操作,并使用代码片段自己键入。

样板文件可能是:

#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
}

因此,该系统的一个示例用法是:

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

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

您可以通过使GenerateDynamicFunction接受参数计数来增强此功能,因此它可以在包装器的开头生成一个检查,表明已经传递了正确数量的参数。如果你在那里放一个哈希表来缓存每个遇到的代码片段的函数,你可以接近原来的例子。 Call函数将采用代码段而不仅仅是API名称,否则将采用相同的名称。它将查找哈希表中的代码片段,如果不存在,它将调用GenerateDynamicFunction并将结果存储在哈希表中以供下次使用。然后它将对该函数执行调用。用法示例:

wchar_t userName[100];

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

当然,除非这个想法是为了打开某种一般的安全漏洞,否则没有太多意义。例如将Call公开为Web服务。您最初的想法存在安全隐患,但不太明显,因为您建议的原始方法不会那么有效。我们制作它的功能越普遍,就会出现更多的安全问题。

根据评论进行更新:

.NET框架有一个名为p / invoke的功能,它正好用于解决您的问题。因此,如果您将此作为一个项目来学习东西,您可以查看p / invoke以了解它的复杂程度。您可以使用脚本语言来定位.NET框架 - 而不是实时解释脚本,或者将它们编译为您自己的字节码,您可以将它们编译为IL。或者,您可以使用.NET上已有的许多脚本语言来托管现有的脚本语言。

答案 4 :(得分:1)

你可以尝试这样的东西 - 它适用于win32 API函数:

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

“stack”参数中的参数应该是相反的顺序(因为这是它们被推入堆栈的顺序)。

答案 5 :(得分:0)

拥有类似这样的功能听起来不错,但你可以试试这个:

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]);
      ...
   }
}

在32位系统上,几乎所有值都适合32位字,较短的值作为函数调用参数的32位字被压入堆栈,因此您应该能够调用几乎所有的Win32 API函数方法,只需将参数转换为int,将int的返回值转换为适当的类型。

答案 6 :(得分:0)

我不确定您是否对它感兴趣,但是可以选择运行RunDll32.exe并让它为您执行函数调用。 RunDll32有一些限制,我不相信你可以访问任何返回值,但如果你正确地形成命令行参数,它将调用该函数。

这是link

答案 7 :(得分:0)

首先,您应该将每个参数的大小添加为额外参数。否则,您需要为每个函数分配每个参数的大小以推入堆栈,这对于WinXX函数是可能的,因为它们必须与它们记录的参数兼容,但是很乏味。

其次,除了varargs函数之外,没有“纯C”方法来调用函数而不知道参数,并且对.DLL中函数使用的调用约定没有约束。

实际上,第二部分比第一部分更重要。

理论上,您可以设置预处理器宏/ #include结构来生成参数类型的所有组合,例如11个参数,但这意味着您提前知道哪些类型将通过您的函数传递{ {1}}。如果你问我,这有点疯狂。

虽然,如果您确实想要不安全地执行此操作,可以传递C ++错位名称并使用Call来提取参数类型。但是,这对于使用C链接导出的函数不起作用。