将 void 指针(数据)转换为函数指针

Posted

技术标签:

【中文标题】将 void 指针(数据)转换为函数指针【英文标题】:Casting a void pointer (data) to a function pointer 【发布时间】:2009-12-08 15:27:53 【问题描述】:

我知道以前有人问过这个问题,但我在这里看到的案例都不是这样的。 我在运行时导入了一些 API 函数,这些函数的一般声明如下:

// Masks for UnmapViewOfFile and MapViewOfFile
typedef BOOL (WINAPI *MyUnmapViewOfFile)(LPCVOID);
typedef LPVOID (WINAPI *MyMapViewOfFile)(HANDLE, DWORD, DWORD, DWORD, SIZE_T);

// Declarations
MyUnmapViewOfFile LoadedUnmapViewOfFile;
MyMapViewOfFile LoadedMapViewOfFile;

然后我调用一个通用的“加载”函数,它调用 GetProcAddress 从正确的 DLL 中获取导出函数的地址。该地址以无效**返回。这个 void** 是通用加载中的参数之一,类似于:

int GenericLoad(char* lib, void** Address, char* TheFunctionToLoad)

我会调用这个函数:

void *Address;
GenericLoad("kernel32.dll", &Address, "UnmapViewOfFile");
LoadedUnmapViewOfFile = (MyUnmapViewOfFile) Address;

或类似的东西。 现在,编译器当然会抱怨试图将数据 void* 强制转换为函数指针。那我该怎么做呢?

我已经阅读了无数的网站和各种讨厌的演员表,所以如果你在解释中添加代码,我将不胜感激。

谢谢 杰斯

【问题讨论】:

“请发送密码”的新形式。当然,这是一个骗局。 我们能找到它的副本吗? @Neil,如果你这么确定,能否请你指点我这篇文章?不,我不是因为我懒惰而要求代码,而是因为我不知道答案。这就是我在这里问的原因。 【参考方案1】:

正确的代码应该是这一行:

GenericLoad("kernel32.dll", (void**)&LoadedUnmapViewOfFile, "UnmapViewOfFile");

这里所做的基本上是这样的:指针变量的地址(您希望放置函数地址的那个)被传递给 GenericLoad - 这基本上是它所期望的。 void** 表示“给我你的指针的地址”。所有的类型转换都是围绕它的魔法。 C 不允许指定“指向任何函数指针的指针”,因此 API 作者更喜欢 void**。

【讨论】:

谢谢!我会试试这个,看看是否没有任何问题【参考方案2】:

现在,编译器当然会抱怨试图将数据 void* 强制转换为函数指针

我的编译器根本不会抱怨您的代码(再说一次,我没有发出警告 - 我使用或多或少的默认选项)。这与来自 MS、GCC 和其他人的各种编译器一起使用。您能否提供有关您正在使用的编译器和编译器选项以及您看到的确切警告的更多详细信息?

也就是说,C 不能保证函数指针可以毫无问题地转换为空指针或从空指针转换,但实际上这在 Windows 上可以正常工作。

如果您想要符合标准的东西,则需要使用“通用”函数指针而不是 void 指针 - C 保证函数指针可以转换为任何其他函数指针并返回而不会丢失,所以这无论您的平台如何,都可以使用。这可能就是 Win32 GetProcAddress() API 的返回值返回 FARPROC 的原因,它只是函数指针的 typedef,指向不带参数(或至少未指定参数)并返回指针大小的 int 的函数。比如:

typedef INT_PTR (FAR WINAPI *FARPROC)();

FARPROC 将是 Win32 的“通用”函数指针的想法。所以你需要做的就是有一个类似的typedef(如果你因为某种原因不想使用FARPROC):

typedef intptr_t (*generic_funcptr_t)();    // intptr_t is typedef'ed appropriately elsewhere, 
                                            //    like in <stdint.h> or something

int GenericLoad(char* lib, generic_funcptr_t* Address, char* TheFunctionToLoad)

generic_funcptr_t Address;
GenericLoad("kernel32.dll", &Address, "UnmapViewOfFile");
LoadedUnmapViewOfFile = (MyUnmapViewOfFile) Address;

或者你可以省去中间人,将你真正想要获取值的指针传递给:

GenericLoad2("kernel32.dll", (generic_funcptr_t *) &LoadedUnmapViewOfFile, "UnmapViewOfFile");

虽然这比使用中间变量的方法更危险 - 例如,如果您在最后一个示例中省略与号,编译器将不会给出诊断,但是在前面的示例中,它通常会给出至少一个警告,如果您从 Address 参数中省略了与号。与此处的错误 #1 类似:http://blogs.msdn.com/sdl/archive/2009/07/28/atl-ms09-035-and-the-sdl.aspx

现在你应该准备好了。然而,无论你怎么看,你都需要进行一些危险的施法。即使在带有模板的 C++ 中,您也必须在某个级别执行强制转换(尽管您可能能够将其隐藏在模板函数中),因为 GetProcAddress() API 不知道您所使用的函数指针的实际类型正在检索。

还请注意,GenericLoad() 的接口可能存在严重的设计问题 - 它无法管理库的生命周期。如果您的意图是不允许卸载库,这可能不是问题,但这是用户可能想要的,因此您应该考虑这个问题。

【讨论】:

【参考方案3】:

数据地址和函数地址是不兼容的东西。我相信您的 Address 变量必须是您的 BOOL 定义中的函数指针:(WINAPI *MyUnmapViewOfFile)(LPCVOID)。这种类型的声明是必需的,因为为了获得指向函数的指针,必须知道返回类型以及参数的类型和数量。这是因为当你调用你的函数时,必须在堆栈上分配正确的空间来包含这些返回值和参数。

鉴于此,我相信 Pavel 答案中的更正是正确的(仅供参考,他的 (void**) cast 是一种类型安全措施)。

【讨论】:

以上是关于将 void 指针(数据)转换为函数指针的主要内容,如果未能解决你的问题,请参考以下文章

如何合法地将函数指针转换为方法指针?

用于成员函数指针往返转换的 void(*)() 类似物

将函数指针的 C 结构体转换为 JNA 代码

如何将地址转换为函数指针以调用方法

为啥指向成员函数的指针不像数据指针那样只是内存地址

将c ++引用的地址传递给指针