这是我用的一些代码:
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);
}
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!
然而,这会导致“structureType参数布局不是顺序的或明确的”错误:
System.ArgumentException:指定的结构体必须是可平坦化的或具有布局信息。
我猜这就是你现在所面对的问题,KBDLLHOOKSTRUCT 声明为 class,并且遇到了“没有布局信息”的错误。有两种方法可以解决这个问题。
首先,根据 Eric Law 的评论,你可以保留 Marshal.PtrToStructure 调用,将 KBDLLHOOKSTRUCT 保持为 class,并向 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));
在这种情况下,如果你愿意,仍然可以为KBDLLHOOKSTRUCT结构添加
[StructLayout(LayoutKind.Sequential)]
属性。虽然在技术上来说是多余的,但有助于你的代码读者将KBDLLHOOKSTRUCT识别为一个布局敏感的Interop类型。
这两种解决方案都对我起作用(虽然只是简单的测试)。从这两种解决方案中,我推荐第二种,因为在Win32 / C结构的P/Invoke场景中,通常会使用
struct
进行声明。此外,以STRUCT结尾的名称可能应该是一个结构体,而不是一个类!
最后,让我提到一种替代方法。
可以将LowLevelKeyboardProc的lParam声明为
ref KBDLLHOOKSTRUCT
(其中KBDLLHOOKSTRUCT是一个结构体,而不是类),而不是IntPtr。这也需要更改CallNextHookEx,但净结果是通过避免Marshal调用来简化对KBDLLHOOKSTRUCT信息的使用。使用ref参数还意味着你可以对结构体进行写入操作(我知道这是你的目标,来自其他问题),而无需在写入后进行marshal操作:
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);
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时,并没有实际影响文本框中出现的内容等。我对低级键盘钩子不了解,不知道为什么会这样,也不知道需要做些什么使其生效;抱歉。)
KBDLLHOOKSTRUCT
被声明为一个struct
。将其转换为一个class
后,该异常消失了。 - c00000fd