Win api in C#. Get Hi and low word from IntPtr

前端 未结 3 741
野的像风
野的像风 2020-12-24 08:36

I am trying to process a WM_MOUSEMOVE message in C#.

What is the proper way to get an X and Y coordinate from lParam which is a type of IntPtr?

相关标签:
3条回答
  • 2020-12-24 09:04

    Usualy, for low-level mouse processing I have used the following helper (it also considers that IntPtr size depends on 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;
    }
    
    0 讨论(0)
  • 2020-12-24 09:18

    Try:
    (note that this was the initial version, read below for the final version)

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

    The unchecked normally isn't necessary (because the "default" c# projects are unchecked)

    Consider that these are the definitions of the used macros:

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

    Where WORD == ushort, DWORD == uint. I'm cutting some ushort->short conversions.

    Addendum:

    one and half year later, and having experienced the "vagaries" of 64 bits .NET, I concur with Celess (but note that 99% of the Windows messages are still 32 bits for reasons of compatibility, so I don't think the problem isn't really big now. It's more for the future and because if you want to do something, you should do it correctly.)

    The only thing I would make different is this:

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

    instead of doing the check "is the IntPtr 4 or 8 bytes long", I take the worst case (8 bytes long) and cast xy to a long. With a little luck the double cast (to long and then to short/to uint) will be optimized by the compiler (in the end, the explicit conversion to int of IntPtr is a red herring... If you use it you are putting yourself at risk in the future. You should always use the long conversion and then use it directly/re-cast it to what you need, showing to the future programmers that you knew what you were doing.

    A test example: http://ideone.com/a4oGW2 (sadly only 32 bits, but if you have a 64 bits machine you can test the same code)

    0 讨论(0)
  • 2020-12-24 09:23

    Correct for both 32 and 64-bit:

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

    - or -

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

    These also work:

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

    - or -

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

    Going the other way

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

    - or -

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

    The accepted answer is good translation of the C definition. If were dealing with just the raw 'void*' directly, then would be mostly ok. However when using 'IntPtr' in a .Net 64-bit execution environment, 'unchecked' will not stop conversion overflow exceptions from being thrown from inside IntPtr. The unchecked block does not affect conversions that happen inside IntPtr funcitons and operators. Currently the accepted answer states that use of 'unchecked' is not necesary. However the use of 'unchecked' is absolutely necessary, as would always be the case in casting to negative values from a larger type.

    On 64-bit, from the accepted answer:

    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  
    

    On 64-bit, using extrapolated version of DmitryG's:

    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)
    

    On performance

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

    The IntPtr.Size property returns a constant as compile time literal that is capable if being inlined across assemblies. Thus is possible for the JIT to have nearly all of this optimized out. Could also do:

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

    - or -

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

    - or -

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

    and all 3 of these will always call the equivalient of IntPtr.ToInt64(). ToInt64(), and 'operator long', are also capable of being inlined, but less likely to be. Is much more code in 32-bit version than the Size constant. I would submit that the solution at the top is maybe more symantically correct. Its also important to be aware of sign-extension artifacts, which would fill all 64-bits reguardless on something like (long)int_val, though i've pretty much glossed over that here, however may additionally affect inlining on 32-bit.

    Useage

    if (Low16(wParam) == NativeMethods.WM_CREATE)) { }
    
    var x = Low16(lParam);
    
    var point = GetPoint(lParam);
    

    A 'safe' IntPtr mockup shown below for future traverlers.

    Run this without setting the WIN32 define on 32-bit to get a solid simulation of the 64-bit IntPtr behavour.

    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
    }
    
    0 讨论(0)
提交回复
热议问题