Marshal.PtrToStructure throwing System.ArgumentException ошибка - PullRequest
10 голосов
/ 17 января 2010

Я пытаюсь получить KBDLLHOOKSTRUCT от lParam с крючком клавиатуры.

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {

        KBDLLHOOKSTRUCT kbd = new KBDLLHOOKSTRUCT();
        Marshal.PtrToStructure(lParam, kbd); // Throws System.ArguementException
        ...

К сожалению, PtrToStructure выбрасывает два

A first chance exception of type 'System.ArgumentException' occurred in myprogram.exe

ошибок при каждом нажатии клавиши. Он также останавливает этот метод на своих треках.

MSNDA говорит: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx

ArgumentException when:

The structureType parameter layout is not sequential or explicit.

-or-

The structureType parameter is a generic type.

Что я могу сделать здесь, чтобы это заработало? lParam идет прямо с крючка клавиатуры, поэтому я ожидаю, что он будет правильным. Есть ли здесь какая-то из этих ошибок, и что я могу сделать, чтобы исправить это?

1 Ответ

32 голосов
/ 17 января 2010

Вот код, который работает для меня:

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, это на самом деле не влияло на то, что отображалось в текстовых полях и т. Д. Я не знаю достаточно о низкоуровневых хуках клавиатуры, чтобы понять, почему нет или что мне нужно сделать, чтобы это сработало; извините.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...