Необъяснимое OverflowException, приводящее IntPtr, представляющий экранную координату к Int32

У нас есть приложение WinForms AnyCPU, в котором элемент управления библиотеки поставщика иногда выдает следующее исключение на 64-битный пользовательский ящик с несколькими мониторами:

System.OverflowException: Arithmetic operation resulted in an overflow.
   at VendorLibraryName.VendorControl.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

Я посмотрел на обработчик WndProc элемента управления библиотеки поставщика, и единственный фрагмент кода, который выглядит так, как будто он может вызвать переполнение, - это (мои комментарии - это декомпилировано):

switch (msg)
{
    case 132: // NCHITTEST 
    case 672: // NCMOUSEHOVER 

    // Technically dangerous: convert IntPtr to Int32 in a 64-bit process.
    // However, note that for these message codes, 
    // LParam represents a "packed" x and y screen-coordinate. 
    // Given my understanding of how this packing occurs, I can't think
    // of how to construct an LParam such that it would overflow an Int32.
    SomeMethod(x: (int)m.LParam & 65535, y: (int)m.LParam >> 16);

    // More code...

Вот фактический IL для преобразований и разбивки битов:

IL_0092: ldarg.1
IL_0093: call instance native int [System.Windows.Forms]System.Windows.Forms.Message::get_LParam()

// As far as I can tell, this is the only instruction on which overflow could occur
IL_0098: call int32 [mscorlib]System.IntPtr::op_Explicit(native int)

IL_009d: ldc.i4 65535
IL_00a2: and
IL_00a3: ldarg.1

// Same thing here...
IL_00a4: call instance native int [System.Windows.Forms]System.Windows.Forms.Message::get_LParam()
IL_00a9: call int32 [mscorlib]System.IntPtr::op_Explicit(native int)

IL_00ae: ldc.i4.s 16
IL_00b0: shr

Очевидно, что эта подпрограмма выглядит подверженной проблемам переполнения, поскольку происходит преобразование Message.LParam (IntPtr) в Int32 в 64-разрядном процессе. На самом деле эта процедура неверна, поскольку она неправильно обрабатывает отрицательные координаты - она ​​выглядит как неправильный порт макросов окон GET_X_LPARAM и GET_Y_PARAM для C#.

Тем не менее, я не могу понять, как можно создать LParam для NCHITTEST / NCMOUSEHOVER, который, на практике, переполняет диапазон Int32. (Я думаю, что младшие 16 бит состоят из 16-битной координаты X со знаком, а остальные биты состоят из 16-битной координаты Y с расширенным знаком. Пожалуйста, исправьте меня, если я ошибаюсь, поскольку это может быть критическим недоразумением).

Я не могу воспроизвести исключение на моем dev-box с множеством различных конфигураций монитора и положений окна.

Какие координаты экрана могут привести к переполнению? Или есть другой способ, которым этот блок может привести к переполнению?

1 ответ

Я думаю, что ключ ваших проблем лежит в "нескольких мониторах". Несколько мониторов могут привести к отрицательным координатам

Из MSDN:

Внимание! Не используйте макросы LOWORD или HIWORD для извлечения x- и y-координат позиции курсора, поскольку эти макросы возвращают неверные результаты в системах с несколькими мониторами. Системы с несколькими мониторами могут иметь отрицательные координаты x и y, а LOWORD и HIWORD рассматривают координаты как беззнаковые величины.

Так как в CLR числа со знаком представлены с использованием нотации из 2-х дополнений, отрицательные числа представляются как "большие" числа без знака (те, чей старший бит равен "1"); например, -1 это 1111....1. Следовательно, переполнение при приведении к (подписанному) 32-битному целому числу.

РЕДАКТИРОВАТЬ: (отказ от ответственности: у меня нет нескольких мониторов, поэтому некоторые догадки применимы) Короче говоря: я предполагаю, что вы должны сгенерировать отрицательную координату Y.

Предположим, что координаты (x: -1, y: -1)

Как короткие числа: x: 0xFFFF, y: 0xFFFF

Упаковано в 32-битное число: 0xFFFF FFFF

Теперь, это где догадки: нет расширения знака для IntPtr (вы можете попробовать это с отладчиком? Вам понадобится отрицательная координата). Поэтому это становится:

0x0000 0000 FFFF FFFF

Или 4294967295. Это слишком большое число, чтобы привести его к Int32.

В общем, любая отрицательная координата Y будет иметь вид

000.(32 Zeros)..001 ...(other 31 digits) .. 0 

и должен поднять проблему (пытались ли вы расположить мониторы один над другим?)

Другие вопросы по тегам