【发布时间】:2017-03-02 19:54:43
【问题描述】:
关于 Win32 SendMessage 函数的正确签名的信息来源存在冲突。
Microsoft 的 winapi 文档声明其签名如下:
LRESULT WINAPI SendMessage(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam );
所以我们有以下类型需要解决:
LRESULTWPARAMLPARAM
使用 Microsoft 的“Windows Data Types”页面,我得到了一些发现。
LRESULT 被键入为LONG_PTR。 LONG_PTR 有如下定义:
#if defined(_WIN64)
typedef __int64 LONG_PTR;
#else
typedef long LONG_PTR;
#endif
根据相同的“Windows Data Types”页面,winapi 中的long 始终是 32 位有符号数。
接下来是WPARAM,输入为UINT_PTR。 UINT_PTR 有如下定义:
#if defined(_WIN64)
typedef unsigned __int64 UINT_PTR;
#else
typedef unsigned int UINT_PTR;
#endif
winapi 中的int(就像long)始终是一个 32 位有符号数。
最后,LPARAM,输入为另一个LONG_PTR。
所以,总而言之,我们有:
-
LRESULT->LONG_PTR-> 签名的 32 位/64 位取决于平台。 -
WPARAM->UINT_PTR-> 无符号 32 位/64 位,取决于平台。 -
LPARAM->LONG_PTR-> 签名 32 位/64 位,具体取决于平台。
因此我们在 C# 中的签名(忽略签名)将是:
[DllImport( "User32.dll", CharSet = CharSet.Auto )]
static extern IntPtr SendMessage( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam );
P-Invoke.Net 的 documentation on SendMessage 证实了这一点 - 他们有几乎相同的签名,除了签名,他们的文档有这样的说法:
2) 切勿使用“int”或“integer”作为 lParam。您的代码将在 64 位 Windows 上崩溃。仅使用 IntPtr、“ref”结构或“out”结构。
3) 切勿使用“bool”、“int”或“integer”作为返回值。您的核心将在 64 位 Windows 上崩溃。仅使用 IntPtr。使用 bool 并不安全 - pInvoke 无法将 IntPtr 编组为布尔值。
到目前为止一切都说得通。
但是,有两条信息不同意这一点。
第一,我自己的代码使用了int wparam 和int lparam,在64 位和32 位下运行了几个月而没有崩溃。为什么没有崩溃?
二,在微软自己的源代码,System.Windows.Forms.UnsafeNativeMethods,我们看到很多很多的定义违反了这个分析。其中最糟糕的是在第 1044 行:
[DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
public static extern IntPtr SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);
他们明确将lParam 声明为一个int,它应该会崩溃。
SendMessage 的正确签名是什么?
微软的签名被破坏了吗?
为什么“损坏”的签名没有导致崩溃?
【问题讨论】:
-
您可以在 64 位上使用它,因为它是一个寄存器调用约定。 IntPtr 是正确的。
-
lParam和wParam实际上是IntPtr(指针大小值)。如果您将 pointer 传递给 64 位进程中的某些数据并且该指针超过 32 位大小 - 它将被截断(如果您将其声明为 int )并且如果您的消息接收者崩溃通过这个无效指针引用数据 -
@RbMm - 在这个问题的上下文中,您的评论没有意义。
-
@DavidHeffernan 你能详细解释一下吗?
-
@DavidHeffernan 是的,但是“你可以在 64 位上摆脱它,因为它是一个寄存器调用约定”
标签: c# windows winforms winapi