15

我正在尝试在 C# 中处理 WM_MOUSEMOVE 消息。

从 IntPtr 类型的 lParam 获取 X 和 Y 坐标的正确方法是什么?

4

3 回答 3

27

尝试:(
请注意,这是初始版本,请阅读下面的最终版本)

IntPtr xy = value;
int x = unchecked((short)xy);
int y = unchecked((short)((uint)xy >> 16));

通常unchecked不需要(因为未选中“默认” c# 项目)

考虑这些是使用的宏的定义:

#define LOWORD(l) ((WORD)(((DWORD_PTR)(l)) & 0xffff))
#define HIWORD(l) ((WORD)((((DWORD_PTR)(l)) >> 16) & 0xffff))

#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))

哪里WORD == ushortDWORD == uint。我正在削减一些 ushort->short 转换。

附录:

一年半后,经历了 64 位 .NET 的“变幻莫测”,我同意 Celess(但请注意,出于兼容性原因,99% 的 Windows 消息仍然是 32 位,所以我认为问题不大现在不是很大。更多的是为了未来,因为如果你想做某事,你应该正确地做。)

我唯一不同的是:

IntPtr xy = value;
int x = unchecked((short)(long)xy);
int y = unchecked((short)((long)xy >> 16));

我没有检查“是IntPtr4 还是 8 字节长”,而是采用最坏的情况(8 字节长)并xy转换为long. 运气好的话,编译器会优化双重转换(tolong然后到short/to )(最后,显式转换为of是一个红鲱鱼......如果你使用它,你会让自己处于危险之中未来。您应该始终使用转换,然后直接使用它/将其重新转换为您需要的,向未来的程序员表明您知道自己在做什么。uintintIntPtrlong

测试示例: http: //ideone.com/a4oGW2(遗憾的是只有 32 位,但如果您有 64 位机器,您可以测试相同的代码)

于 2011-10-27T08:26:42.123 回答
13

适用于 32 位和 64 位:

Point GetPoint(IntPtr _xy)
{
    uint xy = unchecked(IntPtr.Size == 8 ? (uint)_xy.ToInt64() : (uint)_xy.ToInt32());
    int x = unchecked((short)xy);
    int y = unchecked((short)(xy >> 16));
    return new Point(x, y);
}

- 或者 -

int GetIntUnchecked(IntPtr value)
{
    return IntPtr.Size == 8 ? unchecked((int)value.ToInt64()) : value.ToInt32();
}
int Low16(IntPtr value)
{
    return unchecked((short)GetIntUnchecked(value));
}
int High16(IntPtr value)
{
    return unchecked((short)(((uint)GetIntUnchecked(value)) >> 16));
}

这些也有效:

int Low16(IntPtr value)
{
    return unchecked((short)(uint)value);   // classic unchecked cast to uint
}
int High16(IntPtr value)
{
    return unchecked((short)((uint)value >> 16));
}

- 或者 -

int Low16(IntPtr value)
{
    return unchecked((short)(long)value);   // presumption about internals
}                                           //  is what framework lib uses
int High16(IntPtr value)
{
    return unchecked((short)((long)value >> 16));
}

走另一条路

public static IntPtr GetLParam(Point point)
{
    return (IntPtr)((point.Y << 16) | (point.X & 0xffff));
}                                           // mask ~= unchecked((int)(short)x)

- 或者 -

public static IntPtr MakeLParam(int low, int high)
{
    return (IntPtr)((high << 16) | (low & 0xffff));  
}                                           // (IntPtr)x is same as 'new IntPtr(x)'

公认的答案是对 C 定义的良好翻译。如果直接处理原始的“void *”,那么大部分都可以。但是,在 .Net 64 位执行环境中使用“IntPtr”时,“未选中”不会阻止从 IntPtr 内部抛出转换溢出异常。未经检查的块不影响IntPtr 函数和运算符内部发生的转换。目前接受的答案表明不需要使用“未检查”。然而,使用 'unchecked'是绝对必要的,从较大的类型转换为负值时总是如此。

在 64 位上,从接受的答案:

var xy = new IntPtr(0x0FFFFFFFFFFFFFFF);
int x = unchecked((short)xy);                // <-- throws
int y = unchecked((short)((uint)xy >> 16));  // gets lucky, 'uint' implicit 'long'
    y = unchecked((short)((int)xy >> 16));   // <-- throws

    xy = new IntPtr(0x00000000FFFF0000);     // 0, -1
    x = unchecked((short)xy);                // <-- throws
    y = unchecked((short)((uint)xy >> 16));  // still lucky  
    y = (short)((uint)xy >> 16);             // <-- throws (short), no longer lucky  

在 64 位上,使用 DmitryG 的外推版本:

var ptr = new IntPtr(0x0FFFFFFFFFFFFFFF);
var xy = IntPtr.Size == 8 ? (int)ptr.ToInt64() : ptr.ToInt32(); // <-- throws (int)
int x = unchecked((short)xy);                // fine, if gets this far
int y = unchecked((short)((uint)xy >> 16));  // fine, if gets this far
    y = unchecked((short)(xy >> 16));        // also fine, if gets this far

    ptr = new IntPtr(0x00000000FFFF0000);    // 0, -1
    xy = IntPtr.Size == 8 ? (int)ptr.ToInt64() : ptr.ToInt32(); // <-- throws (int)

关于性能

return IntPtr.Size == 8 ? unchecked((int)value.ToInt64()) : value.ToInt32();

IntPtr.Size 属性返回一个常量作为编译时文字,如果在程序集中内联该常量。因此,JIT 几乎可以优化所有这些。还可以这样做:

return unchecked((int)value.ToInt64());

- 或者 -

return unchecked((int)(long)value);

- 或者 -

return unchecked((uint)value);           // traditional

并且所有这 3 个都将始终调用 IntPtr.ToInt64() 的等效项。ToInt64() 和 'operator long' 也可以内联,但不太可能。32 位版本中的代码比 Size 常量多得多。我会提出,顶部的解决方案可能在语义上更正确。了解符号扩展伪影也很重要,它会在 (long)int_val 之类的东西上毫无顾忌地填充所有 64 位,尽管我在这里几乎掩盖了这一点,但可能还会影响 32 位的内联。

用途

if (Low16(wParam) == NativeMethods.WM_CREATE)) { }

var x = Low16(lParam);

var point = GetPoint(lParam);

下面显示了一个“安全”的 IntPtr 模型,供未来的旅行者使用。

运行此程序而不在 32 位上设置 WIN32 定义以获得 64 位 IntPtr 行为的可靠模拟。

public struct IntPtrMock
{
    #if WIN32
        int m_value;
    #else
        long m_value;
    #endif

    int IntPtr_ToInt32() {
        #if WIN32
            return (int)m_value;
        #else
            long l = m_value;
            return checked((int)l);
        #endif
    }

    public static explicit operator int(IntPtrMock value) { //(short) resolves here
        #if WIN32 
            return (int)value.m_value;
        #else
            long l = value.m_value;
            return checked((int)l); // throws here if any high 32 bits 
        #endif                      //  check forces sign stay signed
    }

    public static explicit operator long(IntPtrMock value) { //(uint) resolves here
        #if WIN32
            return (long)(int)value.m_value; 
        #else
            return (long)value.m_value;
        #endif 
    }

    public int ToInt32() {
        #if WIN32 
            return (int)value.m_value;
        #else
            long l = m_value;
            return checked((int)l); // throws here if any high 32 bits 
        #endif                            //  check forces sign stay signed
    }

    public long ToInt64() {
        #if WIN32
            return (long)(int)m_value; 
        #else
            return (long)m_value;
        #endif
    }

    public IntPtrMock(long value) { 
        #if WIN32
            m_value = checked((int)value);
        #else
            m_value = value; 
        #endif
    }

}

public static IntPtr MAKELPARAM(int low, int high)
{
    return (IntPtr)((high << 16) | (low & 0xffff));
}

public Main()
{
    var xy = new IntPtrMock(0x0FFFFFFFFFFFFFFF); // simulate 64-bit, overflow smaller

    int x = unchecked((short)xy);                // <-- throws
    int y = unchecked((short)((uint)xy >> 16));  // got lucky, 'uint' implicit 'long'
        y = unchecked((short)((int)xy >> 16));   // <-- throws

    int xy2 = IntPtr.Size == 8 ? (int)xy.ToInt64() : xy.ToInt32();   // <-- throws
    int xy3 = unchecked(IntPtr.Size == 8 ? (int)xy.ToInt64() : xy.ToInt32()); //ok

    // proper 32-bit lParam, overflow signed
    var xy4 = new IntPtrMock(0x00000000FFFFFFFF);       // x = -1, y = -1
    int x2 = unchecked((short)xy4);                                  // <-- throws
    int xy5 = IntPtr.Size == 8 ? (int)xy4.ToInt64() : xy4.ToInt32(); // <-- throws

    var xy6 = new IntPtrMock(0x00000000FFFF0000);       // x = 0, y = -1
    int x3 = unchecked((short)xy6);                                  // <-- throws
    int xy7 = IntPtr.Size == 8 ? (int)xy6.ToInt64() : xy6.ToInt32(); // <-- throws

    var xy8 = MAKELPARAM(-1, -1);                       // WinForms macro 
    int x4 = unchecked((short)xy8);                                  // <-- throws
    int xy9 = IntPtr.Size == 8 ? (int)xy8.ToInt64() : xy8.ToInt32(); // <-- throws
}
于 2013-07-27T21:46:38.383 回答
4

通常,对于低级鼠标处理,我使用了以下帮助程序(它还认为 IntPtr 大小取决于 x86/x64):

//...
Point point = WinAPIHelper.GetPoint(msg.LParam);
//...
static class WinAPIHelper {
    public static Point GetPoint(IntPtr lParam) {
        return new Point(GetInt(lParam));
    }
    public static MouseButtons GetButtons(IntPtr wParam) {
        MouseButtons buttons = MouseButtons.None;
        int btns = GetInt(wParam);
        if((btns & MK_LBUTTON) != 0) buttons |= MouseButtons.Left;
        if((btns & MK_RBUTTON) != 0) buttons |= MouseButtons.Right;
        return buttons;
    }
    static int GetInt(IntPtr ptr) {
        return IntPtr.Size == 8 ? unchecked((int)ptr.ToInt64()) : ptr.ToInt32();
    }
    const int MK_LBUTTON = 1;
    const int MK_RBUTTON = 2;
}
于 2011-10-27T14:48:20.787 回答