无法解释的OverflowException将代表屏幕坐标的IntPtr转换为Int32

本文关键字:IntPtr 转换 Int32 屏幕坐标 OverflowException 无法解释 | 更新日期: 2023-09-27 18:01:26

我们有一个WinForms AnyCPU应用程序,其中供应商库控件偶尔会在运行多个监视器的64位用户盒上抛出以下异常:

System.OverflowException: Arithmetic operation resulted in an overflow.
   at VendorLibraryName.VendorControl.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

我查看了供应商库控件的WndProc处理程序,唯一看起来可能会产生溢出的代码是这样的(注释是我的-这是反编译的):

switch (msg)
{
    case 132: // NCHITTEST 
    case 672: // NCMOUSEHOVER 
    // Technically dangerous: convert IntPtr to Int32 in a 64-bit process.
    // However, note that for these message codes, 
    // LParam represents a "packed" x and y screen-coordinate. 
    // Given my understanding of how this packing occurs, I can't think
    // of how to construct an LParam such that it would overflow an Int32.
    SomeMethod(x: (int)m.LParam & 65535, y: (int)m.LParam >> 16);
    // More code...

下面是实际的IL转换和位偏移:

IL_0092: ldarg.1
IL_0093: call instance native int [System.Windows.Forms]System.Windows.Forms.Message::get_LParam()
// As far as I can tell, this is the only instruction on which overflow could occur
IL_0098: call int32 [mscorlib]System.IntPtr::op_Explicit(native int)
IL_009d: ldc.i4 65535
IL_00a2: and
IL_00a3: ldarg.1
// Same thing here...
IL_00a4: call instance native int [System.Windows.Forms]System.Windows.Forms.Message::get_LParam()
IL_00a9: call int32 [mscorlib]System.IntPtr::op_Explicit(native int)
IL_00ae: ldc.i4.s 16
IL_00b0: shr

显然,这个例程看起来容易出现溢出问题,因为有Message的转换。64位进程中的LParam (IntPtr)为Int32。事实上,这个例程是错误的,因为它没有正确地处理负坐标——它看起来像是将windows的GET_X_LPARAM和GET_Y_PARAM宏错误地移植到c#中。

然而,我无法看到如何为NCHITTEST/NCMOUSEHOVER构建LParam,这在实际中会溢出Int32的范围。(I 认为下16位由带符号的 16位X坐标组成,其余位由带符号扩展的16位Y坐标组成。如果我错了,请纠正我,因为这可能是一个严重的误解)。

我无法用许多不同的监视器配置和窗口位置在我的开发盒上重现异常。

什么屏幕坐标会导致溢出?或者这个块是否有其他方式导致溢出?

无法解释的OverflowException将代表屏幕坐标的IntPtr转换为Int32

我认为你的问题的关键在于"多显示器"。多个监视器可能导致负坐标

从MSDN:

重要不要使用LOWORD或HIWORD宏来提取x-和光标位置的y坐标,因为这些宏返回在具有多个监视器的系统上出现不正确的结果。系统与多个监视器可以具有负x和负y坐标,以及LOWORD和HIWORD将坐标视为无符号量。

由于在CLR上有符号数使用2补数表示法表示,因此负数表示为"大"无符号数(最高有效位为"1"的数);例如-1表示1111....1。因此,转换为(有符号的)32位整数时会溢出。

编辑:(免责声明:我没有多台显示器,所以有些猜测适用)简而言之:我猜你必须生成一个负的y坐标。

假设坐标为(x: -1, y: -1)

作为短数字:x: 0xFFFF, y: 0xFFFF

以32位数字封装:0xFFFF FFFF

现在,这就是涉及到猜测的地方:没有符号扩展到IntPtr(你可以用调试器尝试吗?你需要一个负坐标)。因此它变成了:

0x0000 0000 FFFF FFFF

或者4294967295,这个数字太大了,不能转换为Int32。

一般情况下,任何负Y坐标的形式都是

000.(32 Zeros)..001 ...(other 31 digits) .. 0 

,应该提出问题(您是否尝试过将显示器一个堆叠在另一个上面?)