17

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?

HelloWorld
  • 1,061
  • 2
  • 15
  • 25

3 Answers3

29

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)

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Re: "The unchecked normally isn't necessary" On 64-bit int (or short) cast on IntPtr will throw because the explicit operator is checked. See DmitryG answer. – Beeeaaar Jul 27 '13 at 20:14
  • @Celess Corrected, and +1 for your long response! – xanatos Jul 29 '13 at 10:04
  • :) Yeah delayed response. I would also add that coods bearinbg lParam 'in the wild' may easily come from origins other than windows itself or whatever you are testing against. The original response would not have behaved the same as the C macro. – Beeeaaar Jul 29 '13 at 17:00
  • It still imporant to note that even with only 32-bit values, negative values would still throw on unchecked((short)xy) if y was negative. Ala: unchecked((short)checked((int)(long)value)) :) – Beeeaaar Jul 29 '13 at 18:33
  • Can you directly cast `IntPtr`? I've always used the `ToXXX()` member methods for getting the data into an integer. – Ben Voigt Jul 29 '13 at 20:10
  • @Ben Voigt Yes, there are opertators for casting both to and from. There are only few explicit conversion operators however, which can cause issues, so beware. See the MAKELPARAM 'macro' in the code on my answer for an example. – Beeeaaar Jul 29 '13 at 20:23
  • @BenVoigt Yes, see http://msdn.microsoft.com/en-us/library/aa328693(v=vs.71).aspx and http://msdn.microsoft.com/en-us/library/aa328694(v=vs.71).aspx – xanatos Jul 30 '13 at 08:42
  • 1
    What I gather from this is that: **1.** You must cast from `IntPtr` to `long` because [downcasting further could result in an `OverflowException`](https://msdn.microsoft.com/en-us/library/9a8d37fb%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) on 64-bit machines. **2.** On both 32-bit & 64-bit machines, ONLY the lowest 32 bits are used for the `lParam`, so it is safe to downcast without loss of information. **3.** We cast to `short` to ensure we are only taking the 16 bits of interest. Does that sound correct? – Nicholas Miller Feb 06 '18 at 23:58
  • @NickMiller Yes exactly as you said – xanatos Feb 07 '18 at 09:45
13

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
}
Beeeaaar
  • 1,010
  • 9
  • 19
  • @LarsTech I don't really come here a lot, and formatting the code was fun. Things evolved and some questions were asked. I'd rather it was right. Is there a constructive concern? – Beeeaaar Jul 29 '13 at 21:28
  • @LarsTech Well here are some socially constructive and relivant questions, while you poke fun at me. Do you agree that "explicit conversion to int of IntPtr is a red herring"? Do you think its ok to be accepting of, and cloud issues around, basic programming mechanics to protect your rep, at the expense of "future programmers" who may not understand? – Beeeaaar Jul 29 '13 at 22:00
  • LarsTech comments deleted – Beeeaaar Jul 29 '13 at 22:32
  • He basically made fun of the number of edits, then said he was just poking fun. LarsTech has ~30k, accepted ~18k. – Beeeaaar Jul 29 '13 at 22:45
  • Yes, I made a comment that you were at 26 edits and counting, and I answered your question that no, there was no concern. I have no idea what your issue with rep has to do with this. Yes, please, go ahead and continue to edit your answer to get it right. Somehow I offended you, so I apologize. Let's move on. – LarsTech Jul 29 '13 at 22:56
  • 2
    He actually said "..., done yet?" Could you guys just be constructive? Got 18k and 30k rep guys, one saying my post was a red herring, and the other poking fun at my edits. Thats not conducive. end of story. – Beeeaaar Jul 29 '13 at 22:58
  • Thanks to perfectly answer! – jhlee.8804 May 27 '15 at 02:14
4

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;
}
DmitryG
  • 17,677
  • 1
  • 30
  • 53
  • This might be a better answer. The size is good to be aware of. On 64-bit systems the explicit cast operator on IntPtr for int is checked and will throw. The accepted answer says "The unchecked normally isn't necessary" which isnt true. – Beeeaaar Jul 27 '13 at 20:10
  • @Celess This cast **can throw** when using casting of x64-IntrPtr in general cases. But when `IntPtr` is a correct `Point` it will **never** throw! Thus the code above is complete. – DmitryG Jul 29 '13 at 07:10
  • A signed Y in lParam will cause (int)ptr.ToInt64() to throw. Updated my answer. – Beeeaaar Jul 29 '13 at 17:43
  • @Celess Ohh...i'm sorry. It appears that there was some misunderstanding in our discussion from my side. This is right, that checked/unchecked state should be taken into account. Updated my answer, thanks. – DmitryG Jul 30 '13 at 07:10