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 抛出了两个

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

与您的代码的关键区别在于我调用 Marshal.PtrToStructure(IntPtr, Type) 重载而不是 (IntPtr, object) 重载。我认为这就是你出问题的地方。因为如果使用结构调用 (IntPtr, object) 重载,则会收到以下错误:

System.ArgumentException:结构不能是值类。

此错误的明显修复方法是将 KBDLLHOOKSTRUCT 更改为类(引用类型)而不是结构(值类型):

public class KBDLLHOOKSTRUCT    // not necessarily the right solution!

但是,这会导致 MSDN 所指的错误“结构类型参数布局不是顺序的或显式的。”:

System.ArgumentException:指定的结构必须是 blittable 或具有布局信息。

我猜这就是您现在所处的位置,将 KBDLLHOOKSTRUCT 声明为一个类,并得到“无布局信息”错误。有两种方法可以解决这个问题。

首先,根据 Eric Law 的评论,您可以保持 Marshal.PtrToStructure 调用不变,将 KBDLLHOOKSTRUCT 保留为一个类,并将布局信息添加到 KBDLLHOOKSTRUCT:

[StructLayout(LayoutKind.Sequential)]
public class KBDLLHOOKSTRUCT { ... }

其次,根据我上面的示例代码,您可以将 KBDLLHOOKSTRUCT 更改为 astruct而不是 a 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,不如将其声明为接收 a ref KBDLLHOOKSTRUCT(其中 KBDLLHOOKSTRUCT 是 a struct,而不是 a class)。这也需要对 CallNextHookEx 进行更改,但最终结果是通过完全避免 Marshal 调用来简化 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 回答