如何在Silverlight中使用PInvokes调试与内存相关的错误

时间:2014-02-12 01:40:15

标签: c# .net silverlight winapi pinvoke

在运行带有几个PInvokes的Silverlight 5.0应用程序5分钟后,我收到以下错误:

Attempted to read or write protected memory

可能,我在某处发生了内存泄漏。

问题是如何调试?我根本没有得到堆栈跟踪,所以我无法准确找出有问题的代码。

通过多次注释代码并重新运行应用程序,我认为我设法找到问题,但我无法弄清楚问题。

潜在的违规代码:

private void InitUSBEvents()
{
    const string clsName = "SLUsbClass";
    const string wndName = "SLUsbWindow";

    Win32.WNDCLASSEX wndClassEx = new Win32.WNDCLASSEX();

    wndClassEx.cbSize = Marshal.SizeOf(wndClassEx);
    wndClassEx.lpszClassName = clsName;
    wndClassEx.lpfnWndProc = WndProc;

    rClassAtomValue = Win32.RegisterClassEx2(ref wndClassEx);

    windowHandle = Win32.CreateWindowEx2(0, rClassAtomValue, wndName, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

    Win32Usb.RegisterKeyboardUsbEvents(windowHandle);
}

[AllowReversePInvokeCalls]
private IntPtr WndProc(IntPtr hWnd, WM msg, IntPtr wParam, IntPtr lParam)
{
    switch (msg)
    {
        case WM.INPUT:
            //Console.WriteLine("Key Event");
            break;
        default:
            return Win32.DefWindowProc(hWnd, msg, wParam, lParam);
    }

    return IntPtr.Zero;
}

PInvoke相关声明:

public class Win32
{
    [DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
    public static extern IntPtr CreateWindowEx(
       WindowStylesEx dwExStyle,
       string lpClassName,
       string lpWindowName,
       WindowStyles dwStyle,
       int x,
       int y,
       int nWidth,
       int nHeight,
       IntPtr hWndParent,
       IntPtr hMenu,
       IntPtr hInstance,
       IntPtr lpParam);

    // Create a window, but accept a atom value.  
    [DllImport("user32.dll", SetLastError = true, EntryPoint = "CreateWindowEx")]
    public static extern IntPtr CreateWindowEx2(
       WindowStylesEx dwExStyle,
       UInt16 lpClassName,
       string lpWindowName,
       WindowStyles dwStyle,
       int x,
       int y,
       int nWidth,
       int nHeight,
       IntPtr hWndParent,
       IntPtr hMenu,
       IntPtr hInstance,
       IntPtr lpParam);

    [DllImport("kernel32.dll", EntryPoint = "LocalAlloc")]
    internal static extern IntPtr LocalAlloc_NoSafeHandle(
        LocalMemoryFlags uFlags, IntPtr sizetdwBytes);

    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr LocalFree(IntPtr hMem);

    [DllImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassEx")]
    public static extern UInt16 RegisterClassEx2([In] ref WNDCLASSEX lpwcx); 

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.U2)]
    public static extern short RegisterClassEx([In] ref WNDCLASSEX lpwcx);

    [DllImport("user32.dll")]
    public static extern IntPtr DefWindowProc(IntPtr hWnd, WM uMsg, IntPtr wParam, IntPtr lParam); 
  }

  public class Win32Usb
  {

    public static bool RegisterKeyboardUsbEvents(IntPtr hWnd)
    {
        //Create an array of all the raw input devices we want to 
        //listen to. In this case, only keyboard devices.
        //RIDEV_INPUTSINK determines that the window will continue
        //to receive messages even when it doesn't have the focus.
        RAWINPUTDEVICE[] rid = new RAWINPUTDEVICE[1];

        rid[0].usUsagePage = 0x01;
        rid[0].usUsage = 0x06;
        rid[0].dwFlags = RIDEV_INPUTSINK;
        rid[0].hwndTarget = hWnd;

        return RegisterRawInputDevices(rid, (uint)rid.Length, (uint)Marshal.SizeOf(rid[0]));
    }

    [StructLayout(LayoutKind.Explicit)]
    internal class RAWINPUT
    {
        [FieldOffset(0)]
        public RAWINPUTHEADER header;
        [FieldOffset(16)]
        public RAWMOUSE mouse;
        [FieldOffset(16)]
        public RAWKEYBOARD keyboard;
        [FieldOffset(16)]
        public RAWHID hid;
    }

            [StructLayout(LayoutKind.Sequential)]
    internal class RAWINPUTDEVICELIST
    {
        public IntPtr hDevice;
        [MarshalAs(UnmanagedType.U4)]
        public int dwType;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct RAWINPUTDEVICE
    {
        [MarshalAs(UnmanagedType.U2)]
        public ushort usUsagePage;
        [MarshalAs(UnmanagedType.U2)]
        public ushort usUsage;
        [MarshalAs(UnmanagedType.U4)]
        public int dwFlags;
        public IntPtr hwndTarget;
    }
  }

我也无法找到PInvoke声明的任何错误。在Silverlight上调试PInvoke相关错误有哪些好的策略?或者有没有办法强制堆栈跟踪输出? (我尝试在Debug->异常中启用所有异常)

2 个答案:

答案 0 :(得分:2)

您在WndProc结构中传递的wndClassEx委托正在被垃圾收集。将委托传递给非托管代码时,委托只会在调用非托管函数时保持活动状态。一旦调用结束,就不再在Silverlight应用程序中引用它,因此垃圾收集会将其视为已死并将收集它。

This example of creating a window class in Silverlight在静态方法中缓存WndProc委托,以便不会收集垃圾。

另请参阅P / Invoke上的这个(较旧但仍然有效)MSDN article

答案 1 :(得分:1)

@ shf301是正确的,你需要采取措施阻止你的代表被收集。我不会在这里重复他的观点。

但是,除此问题外,您的结构定义不正确。它们将工作在32位以下,但不低于64位。使用FieldOffset的经验法则是您只使用偏移量0。此规则允许您编写适用于32位和64位的代码。在您的代码中,您使用了16的偏移量,这对于32位是好的,但不是64位。按照这种模式定义结构:

[StructLayout(LayoutKind.Explicit)]
struct RAWINPUTUNION
{
    [FieldOffset(0)]
    public RAWMOUSE mouse;
    [FieldOffset(0)]
    public RAWKEYBOARD keyboard;
    [FieldOffset(0)]
    public RAWHID hid;
}

[StructLayout(LayoutKind.Sequential)]
struct RAWINPUT
{
    public RAWINPUTHEADER header;       
    public RAWINPUTUNION data;
}