【问题标题】:How to debug memory related errors from using PInvokes in Silverlight如何在 Silverlight 中使用 PInvokes 调试与内存相关的错误
【发布时间】:2014-02-12 01:40:14
【问题描述】:

在运行包含多个 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->Exception中打开所有要抛出的异常)

【问题讨论】:

  • 您的大部分MarshalAs 属性都可以删除。在我看来,它们只是增加了噪音。

标签: c# .net silverlight winapi pinvoke


【解决方案1】:

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

This example of creating a window class in SilverlightWndProc 委托缓存在一个静态方法中,这样它就不会被垃圾回收。

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

【讨论】:

  • 我能够识别并解决问题,非常感谢。但是,我仍然对如何调试此类问题感到好奇,因为我没有得到关于错误的堆栈跟踪。
【解决方案2】:

@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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-09
    • 1970-01-01
    • 2010-09-20
    • 2014-04-13
    相关资源
    最近更新 更多