Необъяснимое 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
и должен поднять проблему (пытались ли вы расположить мониторы один над другим?)