Marshal.PtrToStructure抛出System.ArgumentException错误

时间:2010-01-17 03:33:17

标签: c# marshalling 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直接来自键盘钩子,所以我希望它是正确的。这些错误中的任何一个都有意义吗,我该怎么做才能解决它?

1 个答案:

答案 0 :(得分:32)

以下是一些适合我的代码:

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的错误“结构类型参数布局不是顺序的或显式的”。<:p>

  

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信息的 use 。使用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时,它实际上并没有影响文本框中出现的内容等。我不太了解低级键盘钩子知道为什么不或者我需要做些什么才能使这项工作;对不起。)