为了允许从脚本语言(用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看起来可能是使用合适的机制实现的。任何细节都将不胜感激。
答案 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对函数使用至少两个不同的调用约定:stdcall
和cdecl
。你需要处理这两个问题,甚至可能需要弄清楚要使用哪个。
解决此问题的一种方法是使用现有库来封装许多细节。令人惊讶的是,有一个: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链接导出的函数不起作用。