Marshal.PtrToStructure抛出System.ArgumentException错误

我试图从键盘钩子的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直接来自键盘钩子,所以我希望它是正确的。 这些错误中的任何一个都有意义吗,我该怎么做才能修复它?

这是一些适合我的代码:

 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更改为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场景中被声明为struct – 如果没有其他的东西,以STRUCT结尾的名称应该是结构而不是类!

最后,让我提一个替代方法。

不是将LowLevelKeyboardProc声明为接收IntPtr作为其lParam,而是可以将其声明为接收ref KBDLLHOOKSTRUCT (其中KBDLLHOOKSTRUCT是struct ,而不是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时,它实际上并没有影响文本框中出现的内容等。我不太了解低级键盘钩子知道为什么不是或我是什么我需要这样做才能做到这一点;抱歉。)