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

前端 未结 3 780
野的像风
野的像风 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: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
    }
    

提交回复
热议问题