这是一些对我有用的代码:
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 时,它实际上并没有影响文本框等中出现的内容。我对低级键盘钩子知之甚少,不知道为什么不知道或者我是什么需要做才能完成这项工作;对不起。)