【问题标题】:What is the true signature of SendMessage, and how should it be invoked in C#?SendMessage 的真正签名是什么,在 C# 中应该如何调用它?
【发布时间】: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 );

所以我们有以下类型需要解决:

  • LRESULT
  • WPARAM
  • LPARAM

使用 Microsoft 的“Windows Data Types”页面,我得到了一些发现。


LRESULT 被键入为LONG_PTRLONG_PTR 有如下定义:

#if defined(_WIN64)
 typedef __int64 LONG_PTR; 
#else
 typedef long LONG_PTR;
#endif

根据相同的“Windows Data Types”页面,winapi 中的long 始终是 32 位有符号数。


接下来是WPARAM,输入为UINT_PTRUINT_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 wparamint 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 是正确的。
  • lParamwParam 实际上是 IntPtr(指针大小值)。如果您将 pointer 传递给 64 位进程中的某些数据并且该指针超过 32 位大小 - 它将被截断(如果您将其声明为 int )并且如果您的消息接收者崩溃通过这个无效指针引用数据
  • @RbMm - 在这个问题的上下文中,您的评论没有意义。
  • @DavidHeffernan 你能详细解释一下吗?
  • @DavidHeffernan 是的,但是“你可以在 64 位上摆脱它,因为它是一个寄存器调用约定”

标签: c# windows winforms winapi


【解决方案1】:

在 64 位 Windows (AMD64) 上,first 4 function parameters 存储在 CPU 寄存器中,它们不会在堆栈上传递。这些寄存器始终存在并且始终为 64 位宽,因此对函数的实际调用始终是“有效的”。但是参数的内容可能无效。

  • 当参数指定为 int 而不是 IntPtr 时,高 32 位的内容未定义。编译器/编组器很可能会生成一个 mov(复制到寄存器中)来清除高 32 位,但不能保证,未来的编译器优化可能会将“空闲”空间用于其他用途。

  • 在运行时,大多数情况下您不会看到崩溃,只有在进程地址空间中的前 4 GiB 之外分配了一些内存时才会发生崩溃。您可以通过使用MEM_TOP_DOWN 标志调用VirtualAlloc 来强制进行此类分配。如果地址不适合 32 位,则地址将被截断,响应消息的代码将访问错误的地址,并且可能会崩溃或损坏内存或以其他方式失败。

    李>

返回值也存储在寄存器中,具有相同的截断问题。

我不知道为什么损坏的签名不会导致崩溃,也许他们只是发送不使用指针的消息?还是他们只是走运?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-16
    • 2019-05-01
    • 2011-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-09
    相关资源
    最近更新 更多