13

キーボードフックのlParamからKBDLLHOOKSTRUCTを取得しようとしています。

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

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

残念ながら、PtrToStructureは2つを投げています

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はキーボードフックから直接来ているので、正しいと思います。これらのエラーのいずれかがここで意味をなしますか、それを修正するために何ができますか?

4

1 に答える 1

35

これが私のために働くいくつかのコードです:

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);
}

コードとの決定的な違いは、(IntPtr、object)オーバーロードではなく、Marshal.PtrToStructure(IntPtr、Type)オーバーロードを呼び出していることです。そして、私はこれがあなたにとって物事がうまくいかなかったところだと思います。構造体を使用して(IntPtr、object)オーバーロードを呼び出すと、次のエラーが発生するためです。

System.ArgumentException:構造体は値クラスであってはなりません。

このエラーの明らかな修正は、KBDLLHOOKSTRUCTを構造体(値型)ではなくクラス(参照型)に変更することです。

public class KBDLLHOOKSTRUCT    // not necessarily the right solution!

ただし、これにより、MSDNが「structureTypeパラメーターのレイアウトがシーケンシャルまたは明示的ではない」という意味のエラーが発生します。

System.ArgumentException:指定された構造はblittableであるか、レイアウト情報を持っている必要があります。

KBDLLHOOKSTRUCTがクラスとして宣言されており、「レイアウト情報がありません」というエラーが発生しているので、これが現在の場所だと思います。これに対処するには2つの方法があります。

まず、Eric Lawのコメントに従って、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構造体は通常P/Invokeシナリオのように宣言されているため、2つのうち、2つ目をお勧めstructします。STRUCTで終わる名前は、おそらくクラスではなく構造体である必要があります。

最後に、別のアプローチについて説明します。

LowLevelKeyboardProcをlParamとしてIntPtrを受信するものとして宣言する代わりに、ref KBDLLHOOKSTRUCT(KBDLLHOOKSTRUCTは、structではなく、class)を受信するものとして宣言することができます。これにはCallNextHookExへの変更も必要ですが、最終的な結果は、マーシャル呼び出しを完全に回避することにより、KBDLLHOOKSTRUCT情報の使用を簡素化することです。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を変更しようとしても、テキストボックスなどに表示される内容には実際には影響しなかったことを警告する必要があります。低レベルのキーボードフックについては、理由や私が何をしているのかを十分に理解していません。この作業を行うには、行う必要があります。申し訳ありません。)

于 2010-01-17T09:08:52.670 に答える