C#中的Win API。从IntPtr获取高位和低位。

17

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

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

3个回答

30

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

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));

我不是通过检查IntPtr长度是否为4或8字节来进行操作,而是采用最坏情况(8字节长)并将xy强制转换为long。有一点运气的话,编译器会优化双重转换(先转换为long然后再转换为shortuint)。最终,IntPtr的显式转换成int是一个红色警告...如果您使用它,您将在未来面临风险。您应该始终使用long转换,然后直接使用/重新转换为您需要的内容,向未来的程序员展示您知道自己在做什么。

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


回复:“通常情况下,未经检查是不必要的” 在64位int(或short)上对IntPtr进行转换将会抛出异常,因为显式运算符是已检查的。请参见DmitryG的答案。 - Beeeaaar
@Celess 已经更正,对于你的长篇回复点赞! - xanatos
需要注意的是,即使只有32位的值,在y为负数时,负值仍然会在unchecked((short)xy)中抛出。 Ala: unchecked((short)checked((int)(long)value)) :) - Beeeaaar
@BenVoigt 是的,请参见http://msdn.microsoft.com/en-us/library/aa328693(v=vs.71).aspx和http://msdn.microsoft.com/en-us/library/aa328694(v=vs.71).aspx。 - xanatos
1
我从中得出的结论是:1. 你必须从 IntPtr 转换为 long,因为在64位机器上进一步向下转换可能会导致 OverflowException2. 在32位和64位机器上,仅使用最低的32位作为 lParam,因此可以安全地向下转换而不会丢失信息。 3. 我们将其转换为 short,以确保我们只取感兴趣的16位。这听起来正确吗? - Nicholas Miller
显示剩余4条评论

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' 时,'unchecked' 将无法阻止从 IntPtr 内部抛出转换溢出异常。未经检查的块不会影响在 IntPtr 函数和运算符内发生的转换。目前,接受的答案说明使用 'unchecked' 不是必要的。然而,使用 '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
}

@LarsTech 我不经常来这里,格式化代码很有趣。事情发展了一些问题被问出来。我宁愿它是正确的。是否有建设性的关注? - Beeeaaar
@LarsTech 嗯,这里有一些社交建设性和相关的问题,当你嘲笑我时。你是否同意“IntPtr的显式转换为int是一个红鲱鱼”?你认为接受和云化基本编程机制以保护你的声誉,而牺牲“未来的程序员”可能不理解的东西,这样做可以吗? - Beeeaaar
LarsTech 的评论已被删除。 - Beeeaaar
是的,我评论说你已经进行了26次编辑,并回答了你的问题,没有任何问题。我不知道你对声望有什么问题。是的,请继续编辑你的答案以使其正确。不知怎么得罪了你,所以我道歉了。让我们继续吧。 - LarsTech
2
他实际上说:“...,完成了吗?”你们能不能建设性一点?有18k和30k声望的人,一个说我的帖子是一个红鲱鱼,另一个嘲笑我的编辑。这不是有益的。故事结束。 - Beeeaaar
显示剩余2条评论

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;
}

这可能是一个更好的答案。了解大小很重要。在64位系统上,对于int类型的IntPtr显式转换运算符是被检查的,并且会抛出异常。被接受的答案说“通常不需要未经检查”,这是不正确的。 - Beeeaaar
@Celess 在一般情况下,使用 x64-IntrPtr 的强制转换可能会抛出异常。但是当 IntPtr 是正确的 Point 时,它将永远不会抛出异常!因此,上面的代码是完整的。 - DmitryG
在lParam中有一个带符号的Y将导致(int)ptr.ToInt64()抛出异常。我已更新我的答案。 - Beeeaaar
@Celess 哦...对不起。看来在我们的讨论中有些误解是我造成的。没错,应该考虑到选中/未选中状态。我已经更新了我的答案,谢谢。 - DmitryG

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接