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

发布于 2024-07-14 22:10:51 字数 728 浏览 10 评论 0原文

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

如果没有 2024-07-21 22:10:51

首先,你不能在 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;
}

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;
}
想你的星星会说话 2024-07-21 22:10:51

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

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

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 *.

傲性难收 2024-07-21 22:10:51

其他帖子关于几乎肯定需要汇编或其他非标准技巧来实际进行调用是正确的,更不用说实际调用约定的所有细节了。

Windows DLL 至少使用两种不同的函数调用约定:stdcall 和 cdecl。 您需要同时处理这两者,甚至可能需要弄清楚使用哪一个。

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

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.

孤独岁月 2024-07-21 22:10:51

许多 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 上已有的多种脚本语言中的现有脚本语言。

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.

安静被遗忘 2024-07-21 22:10:51

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

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

長街聽風 2024-07-21 22:10:51

拥有这样的函数听起来是个坏主意,但您可以尝试这样做:

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

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.

[旋木] 2024-07-21 22:10:51

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

这是链接

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

明天过后 2024-07-21 22:10:51

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

其次,除了可变参数函数之外,不存在在不知道参数的情况下调用函数的“纯 C”方法,并且 .DLL 中的函数使用的调用约定没有限制。

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

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

不过,如果您确实想不安全地执行此操作,则可以传递 C++ 损坏的名称并使用 UnDecorateSymbolName 来提取参数的类型。 但是,这不适用于通过 C 链接导出的函数。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文