【问题标题】:Function pointers for winapi functions (stdcall/cdecl)winapi 函数的函数指针 (stdcall/cdecl)
【发布时间】:2025-12-20 18:55:05
【问题描述】:

请有人给我一些关于为 MS winapi 函数创建函数指针的提示吗?我正在尝试为 DefWindowProc (DefWindowProcA/DefWindowProcW) 创建一个指针,但收到此错误:

LRESULT (*dwp)(HWND, UINT, WPARAM, LPARAM) = &DefWindowProc;

error C2440: 'initializing' : cannot convert from 
'LRESULT (__stdcall *)(HWND,UINT,WPARAM,LPARAM)' 
to 'LRESULT (__cdecl *)(HWND,UINT,WPARAM,LPARAM)'

我不知道我需要使用什么,因为我不习惯 MS ascii/wide 宏。顺便说一句,我正在创建一个函数指针来快速破解,不幸的是我没有时间解释原因 - 但无论如何,我认为这个问题对需要创建 winapi 函数指针的人会有帮助。

更新:

此代码有效,但我担心这是不好的做法(并且不遵守 unicode/ascii 编译选项)。我应该定义两个规范吗?

LRESULT (__stdcall* dwp)(HWND, UINT, WPARAM, LPARAM) = &DefWindowProc;

更新 2:

这更好(感谢 nobugz):

WNDPROC dwp = DefWindowProc;

【问题讨论】:

  • 我认为确保您的调用约定匹配没有任何不好的做法。
  • 然而,MS 有 3 种不同的调用约定太疯狂了,他们应该选择一个并坚持下去!
  • MS 不负责 __cdecl,几乎不负责 __fastcall。 64 位代码只有一种调用约定。谁添加另一个将被枪杀。
  • Nobugz,微软也有thiscall,这是methods的默认设置。它与 cdecl、stdcall 或 thiscall 中的任何一个都不兼容。而且微软的 thiscall 和 Borland 的不一样。

标签: c++ winapi function-pointers


【解决方案1】:

像这样修复调用约定不匹配:

LRESULT (__stdcall * dwp)(HWND, UINT, WPARAM, LPARAM) = DefWindowProc;

一个 typedef 可以使它更具可读性:

typedef LRESULT (__stdcall * WindowProcedure)(HWND, UINT, WPARAM, LPARAM);
...
WindowProcedure dwp = DefWindowProc;

但是,<windows.h> 已经有一个 typedef,你不妨使用它:

WNDPROC dwp = DefWindowProc;

【讨论】:

  • 我认为的最佳答案;我将使用WNDPROC dwp = DefWindowProc;
  • 如果需要手动写出类型,最好使用“CALLBACK”而不是“__stdcall”,因为这是标题使用的......
【解决方案2】:

您的原型中缺少__stdcall。除了匹配的原型之外,您还需要匹配的 calling convention。 WINAPI函数都是__stdcall,而C++默认是__cdecl

使用extern "C" { code } 是一个可行的选择。

【讨论】:

  • 我一直认为 extern "C" 和 __stdcall 是两个不同的东西。需要 extern "C" 来强制 C ABI(而不是名称损坏的 c++ ABI。__stdcall 是强制执行正常的 Windows 调用约定,其中被调用函数负责清理堆栈,而不是 __cdecl C 调用约定,其中调用函数清理堆栈。
  • 你是对的,Deus Ex Machina。调用约定与链接无关,这是“extern C”控制的。 “Extern C”会影响名称修改,调用约定也会影响,但我们对此处的名称修改不感兴趣,因为在显示的任何代码中都没有导出或导入 DefWindowProc 和 dwp。
  • 更糟糕的是 stdcall 和 cdecl 足够兼容,如果您使用 extern "C" 而不匹配约定,程序将链接,并且调用将完美运行 - 但从调用返回时,你有损坏的局部变量(如果调用是方法中的最后一件事,你甚至不会注意到,因为返回通常会正常工作)。一个大陷阱等待着粗心的人。 (是的,我不得不调试受此问题困扰的代码。)
  • 此外,如果您让编译器优化掉标准堆栈框架代码,则返回可能会使您的应用程序崩溃。这到底是好是坏还有待商榷——至少它更容易找到。 :)
【解决方案3】:

这不是“疯狂”的 MS ascii/wide 宏,您只有 2 个不同的函数声明。

如果你用“extern C”围绕你的内部函数来装饰你的函数,你会用与原始函数相同的调用类型公开你的函数并且它会编译。

【讨论】:

  • 编译,是的。工作,不完全是。请参阅我对 Kornel 答案的评论。