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

为了允许从脚本语言(用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看起来可能使用合适的机制实现。 任何有关这方面的细节将不胜感激。

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

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

首先要做的事情是:您不能将类型作为参数传递给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; } 

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

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

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

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

如果可以一次性调用的API集合,以及可以从简单字符串表示forms转换的单个参数,那么我就不会感到惊讶了。

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

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

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

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

根据评论更新:

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

你可以尝试这样的东西 – 它适用于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”参数中的参数应该是相反的顺序(因为这是它们被推入堆栈的顺序)。

拥有这样的function听起来像个坏主意,但你可以试试这个:

 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的返回值转换为适当的类型。

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

这是一个链接

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

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

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

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

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