Вот код, который работает для меня:
public struct KBDLLHOOKSTRUCT
{
public Int32 vkCode;
public Int32 scanCode;
public Int32 flags;
public Int32 time;
public IntPtr dwExtraInfo;
}
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
Debug.WriteLine(kbd.vkCode); // ***** your code here *****
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
Принципиальное отличие от вашего кода в том, что я вызываю перегрузку Marshal.PtrToStructure (IntPtr, Type), а не перегрузку (IntPtr, object). И я думаю, что здесь все пошло наперекосяк. Потому что если вы вызываете перегрузку (IntPtr, object) со структурой, вы получите следующую ошибку:
System.ArgumentException: структура не должна быть классом значений.
Очевидное исправление этой ошибки - изменить KBDLLHOOKSTRUCT на класс (ссылочный тип) вместо struct (тип значения):
public class KBDLLHOOKSTRUCT // not necessarily the right solution!
Тем не менее, это приводит к ошибке, которую MSDN имеет в виду, что «расположение параметра StructureType не является последовательным или явным.»:
System.ArgumentException: указанная структура должна быть прозрачной или иметь информацию о макете.
Я предполагаю, что именно здесь вы сейчас находитесь, с KBDLLHOOKSTRUCT, объявленным как класс, и с ошибкой "нет информации о макете". Есть два способа решения этой проблемы.
Во-первых, согласно комментарию Эрика Лоу, вы можете оставить свой вызов Marshal.PtrToStructure как есть, сохранить KBDLLHOOKSTRUCT как класс и добавить информацию о компоновке в KBDLLHOOKSTRUCT:
[StructLayout(LayoutKind.Sequential)]
public class KBDLLHOOKSTRUCT { ... }
Во-вторых, согласно моему примеру кода выше, вы можете изменить KBDLLHOOKSTRUCT на struct
вместо class
и изменить вызов Marshal.PtrToStructure на (IntPtr, Type):
public struct KBDLLHOOKSTRUCT { ... }
KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
(В этом случае вы все равно можете добавить атрибут [StructLayout(LayoutKind.Sequential)]
в структуру KBDLLHOOKSTRUCT, если хотите. Он технически избыточен, но может помочь читателям вашего кода распознать KBDLLHOOKSTRUCT как тип взаимодействия, чувствительный к компоновке.)
Оба эти решения работают для меня в (по общему признанию простом) тесте. Из этих двух я бы порекомендовал второе, потому что структуры Win32 / C обычно объявляются как struct
в сценариях P / Invoke - и если ничто иное, имя, заканчивающееся на STRUCT, вероятно, должно быть структурой, а не классом!
Наконец, позвольте мне упомянуть альтернативный подход.
Вместо объявления LowLevelKeyboardProc как получающего IntPtr в качестве его lParam, можно объявить его как получающий ref KBDLLHOOKSTRUCT
(где KBDLLHOOKSTRUCT - struct
, а не class
). Это также требует изменений в CallNextHookEx, но общий результат заключается в том, чтобы упростить использование информации KBDLLHOOKSTRUCT, полностью избегая вызова Marshal. Использование параметра ref также означает, что вы можете записать в структуру (что я знаю из других вопросов - ваша цель) и вам не нужно маршалировать ее после записи:
private delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT kbd);
private static IntPtr HookCallback(
int nCode, IntPtr wParam, ref KBDLLHOOKSTRUCT kbd)
{
Debug.WriteLine(kbd.vkCode); // look! no marshalling!
return CallNextHookEx(_hookID, nCode, wParam, ref kbd);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, ref KBDLLHOOKSTRUCT kbd);
(хотя я, вероятно, должен предупредить вас, что когда я пытался изменить kbd.vkCode, это на самом деле не влияло на то, что отображалось в текстовых полях и т. Д. Я не знаю достаточно о низкоуровневых хуках клавиатуры, чтобы понять, почему нет или что мне нужно сделать, чтобы это сработало; извините.)