Выиграй API в C #.Получите Привет и низкое слово от IntPtr - PullRequest
13 голосов
/ 27 октября 2011

Я пытаюсь обработать сообщение WM_MOUSEMOVE в C #.

Как правильно получить координаты X и Y от lParam, который является типом IntPtr?

Ответы [ 3 ]

23 голосов
/ 27 октября 2011

Try:
(обратите внимание, что это была первоначальная версия, читайте ниже для окончательной версии)

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 == ushort, DWORD == uint. Я сокращаю некоторые короткие-> короткие преобразования.

Добавление:

Спустя полтора года, испытав «капризы» 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, а затем до short / до uint) будет оптимизировано компилятором (в конце концов, явное преобразование в int из IntPtr - красная сельдь ... Если вы используете его, вы подвергаете себя риску в будущем. Вы всегда должны использовать long преобразование, а затем использовать его напрямую / пересчитать его на то, что вам нужно, показывая будущим программистам, что вы знал что ты делал.

Тестовый пример: http://ideone.com/a4oGW2 (к сожалению, только 32 бита, но если у вас есть 64-битный компьютер, вы можете проверить тот же код)

12 голосов
/ 28 июля 2013

Правильно для 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 *', то все было бы в порядке. Однако при использовании IntPtr в 64-разрядной среде выполнения .Net «непроверенный» не остановит исключения переполнения при преобразовании изнутри 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 () и «оператор long» также могут быть встроенными, но с меньшей вероятностью. Гораздо больше кода в 32-битной версии, чем константа Size. Я бы сказал, что решение наверху может быть более симметрично правильным. Также важно знать об артефактах расширения знака, которые заполняют все 64-битные символы независимо от чего-то типа (long) int_val, хотя я здесь довольно подробно об этом упомянул, однако может дополнительно повлиять на встраивание 32-битных. 1043 *

Useage

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

var x = Low16(lParam);

var point = GetPoint(lParam);

«Безопасный» макет IntPtr, показанный ниже для будущих путешественников.

Запустите без , установив определение WIN32 для 32-битной системы, чтобы получить точную симуляцию 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
}
4 голосов
/ 27 октября 2011

Обычно для низкоуровневой обработки мыши я использовал следующий помощник (он также считает, что размер 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;
}
...