检测到CallbackOnCollectedDelegate

时间:2011-09-15 09:34:23

标签: c# windows messages

我的代码运行5-10分钟后,我一直收到此错误

  

检测到CallbackOnCollectedDelegate   消息:对“CashRecyclerTestapp!MessageMonitor + NativeMethods + WndProc :: Invoke”类型的垃圾回收委托进行了回调。这可能会导致应用程序崩溃,损坏和数据丢失。将委托传递给非托管代码时,托管应用程序必须将它们保持活动状态,直到确保它们永远不会被调用为止。

我正在写一个自动售货机应用程序,我有一个dll,发布我需要在我的应用程序中捕获的消息

这是我的代码我从互联网上获取源代码以捕获消息,它在这里不断收到错误

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Threading;
    /// <summary>
    /// Event handler for the <see cref="MessageMonitor.MessageReceived"/> event.
    /// </summary>
    /// <param name="hwnd">Handle to the window procedure that received the message.</param>
    /// <param name="message">Specifies the message.</param>
    /// <param name="wParam">Specifies additional message information. The content of this parameter depends on the value of the Msg parameter.</param>
    /// <param name="lParam">Specifies additional message information. The content of this parameter depends on the value of the Msg parameter.</param>
    public delegate void MessageMonitorEventHandler(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam);

    /// <summary>
    /// MessageMonitor monitors window messages in a background thread and generates
    /// an event that is invoked in the context of the thread that called WatchMessage.
    /// </summary>
    /// <remarks>
    /// Code is based on Stephen Toub article in June 2007 MSDN Magazine.
    /// http://msdn.microsoft.com/en-us/magazine/cc163417.aspx
    /// 
    /// What's different here is that this code does not require the use of System.Windows.Forms.
    /// Using a Form and Application.Run for this task is like using a jack hammer to pound a
    /// finishing nail.
    /// </remarks>
    public static class MessageMonitor
    {
      /// <summary>
      /// Handle to the monitoring window. This can be passed for event callbacks, or registration methods.
      /// </summary>
      /// <exception cref="Win32Exception"/>
      public static IntPtr WindowHandle
      {
        get
        {
          EnsureMonitorWindowCreated();
          return monitorWindowHandle;
        }
      }

      /// <summary>
      /// Subscribes to a particular message.
      /// </summary>
      /// <remarks>
      /// Although you can call this from any thread, the MessageReceived event will only fire on the thread
      /// that first called WatchMessage, or WindowHandle if that was called first.
      /// </remarks>
      /// <param name="message">Specifies the Windows message to monitor.</param>
      /// <exception cref="Win32Exception"/>
      public static void WatchMessage(int message)
      {
        EnsureMonitorWindowCreated();

        messageSetLock.AcquireWriterLock(Timeout.Infinite);
        try
        {
          messageSet[message] = message;
        }
        finally
        {
          messageSetLock.ReleaseWriterLock();
        }
      }

      /// <summary>
      /// Fired when a monitored message is received by the MessageMonitor
      /// </summary>
      public static  event MessageMonitorEventHandler MessageReceived;

      private static object syncObject = new object();
      private static SynchronizationContext syncContext;
      private static IntPtr monitorWindowHandle = IntPtr.Zero;
      private static ReaderWriterLock messageSetLock;
      private static Dictionary<int, int> messageSet;

      private static void EnsureMonitorWindowCreated()
      {
        lock (syncObject)
        {
          if (messageSetLock == null)
            messageSetLock = new ReaderWriterLock();

          if (messageSet == null)
            messageSet = new Dictionary<int, int>();

          // Get the SynchronizationContext associated with the calling thread. This will be used to post-back
          //  the MessageReceived event.
          // SynchronizationContext.Current is not used because it can return null. AsyncOperationManager.SynchronizationContext
          //  creates a default context if one does not yet exist on the calling thread.
          if (syncContext == null)
            syncContext = AsyncOperationManager.SynchronizationContext;

          if (monitorWindowHandle == IntPtr.Zero)
          {
            int lastWin32Error = 0;

            // This wait event is used to wait for the thread to create the monitoring window.
            using (ManualResetEvent threadCreateSignal = new ManualResetEvent(false))
            {
              Thread thread = new Thread((ThreadStart)delegate
              {
                // Create the window on our background thread so that messages to it are 'seen' by our message pump.
                if ((monitorWindowHandle = CreateWindow()) == IntPtr.Zero)
                  // Access to lastWin32Error, a stack variable outside our scope, is made safe by the reset event.
                  lastWin32Error = Marshal.GetLastWin32Error();

                // Signal our creator that we have (or have not) created the message window.
                threadCreateSignal.Set();

                // Enter message loop only if we successfully created the message window.
                if (monitorWindowHandle != IntPtr.Zero)
                {
                  NativeMethods.MSG msg = new NativeMethods.MSG();
                  while (UnsafeNativeMethods.GetMessage(ref msg, IntPtr.Zero, 0, 0))
                    UnsafeNativeMethods.DispatchMessage(ref msg);
                }
              });
              thread.Name = "MessageMonitorThread";
              thread.IsBackground = true;
              thread.Start();
              threadCreateSignal.WaitOne();
            }

            if (lastWin32Error != 0)
              throw new Win32Exception(lastWin32Error);
          }
        }
      }

      private static IntPtr CreateWindow()
      {
        IntPtr hWnd = IntPtr.Zero;
        string className = RegisterWndClass();
        if (className != null)
          // Note that we do not use a "message only window" on purpose. Broadcast messages are never
          //  sent to them and so we just use a regular window to ensure that they too can be monitored.
          return UnsafeNativeMethods.CreateWindowEx(0, className, className, 0, 0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, null);
        return hWnd;
      }

      private static string RegisterWndClass()
      {
        NativeMethods.WNDCLASS wc = new NativeMethods.WNDCLASS();
        wc.lpfnWndProc = WndProc;

        // This will keep trying if it has to, varying the class name until it gets one. This
        //  assures that we will get our own window class object. Overkill, I know, but I like
        //  to leave no loose ends.
        for (int n = 1; n < int.MaxValue; n++)
        {
          wc.lpszClassName = String.Format(CultureInfo.InvariantCulture, "MessageMonitorWindow:{0}", n);
          short ret = UnsafeNativeMethods.RegisterClass(wc);
          if (ret != 0)
            return wc.lpszClassName;
          else if (Marshal.GetLastWin32Error() != NativeMethods.ERROR_CLASS_ALREADY_EXISTS)
            return null;
          // ret == 0 && ERROR_CLASS_ALREADY_EXISTS, try again
        }
        return null;
      }

      public  static IntPtr WndProc(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam)
      {
        messageSetLock.AcquireReaderLock(Timeout.Infinite);
        bool watchingThisMessage = messageSet.ContainsKey(message);
        messageSetLock.ReleaseReaderLock();

        if (watchingThisMessage)
        {
          // We have to package these values to a struct for the Post call, NativeMethods.MSG is
          //  just covenient. The handler doesn't use this structure because I don't want to 
          //  expose it, and since I don't want to create yet another message structure for the
          //  handler, I package them here, and unpack them when invoking the handler.
          NativeMethods.MSG msg = new NativeMethods.MSG();
          msg.hwnd = hwnd;
          msg.message = message;
          msg.wParam = wParam;
          msg.lParam = lParam;

          // Post the invocation of the event handler to the thread that is interested in the event.
          syncContext.Post(delegate(object state)
          {
            MessageMonitorEventHandler handler = MessageReceived;
            if (handler != null)
            {
              NativeMethods.MSG msgCopy = (NativeMethods.MSG)state;
              handler(msgCopy.hwnd, msgCopy.message, msgCopy.wParam, msgCopy.lParam);
            }
          }, msg);
        }

        return UnsafeNativeMethods.DefWindowProc(hwnd, message, wParam, lParam);
      }

      #region PInvoke

      private static class NativeMethods
      {
        public const int ERROR_CLASS_ALREADY_EXISTS = 0x582;

        public delegate IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class WNDCLASS
        {
          public int style;
          public WndProc lpfnWndProc;
          public int cbClsExtra;
          public int cbWndExtra;
          public IntPtr hInstance = IntPtr.Zero;
          public IntPtr hIcon = IntPtr.Zero;
          public IntPtr hCursor = IntPtr.Zero;
          public IntPtr hbrBackground = IntPtr.Zero;
          public string lpszMenuName;
          public string lpszClassName;
        }

        [Serializable, StructLayout(LayoutKind.Sequential)]
        public struct MSG
        {
          public IntPtr hwnd;
          public int message;
          public IntPtr wParam;
          public IntPtr lParam;
          public int time;
          public int pt_x;
          public int pt_y;
        }
      }

      [SuppressUnmanagedCodeSecurity]
      private static class UnsafeNativeMethods
      {
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hWndParent, IntPtr hMenu, IntPtr hInst, [MarshalAs(UnmanagedType.AsAny)] object pvParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr DefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr DispatchMessage([In] ref NativeMethods.MSG msg);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool GetMessage([In, Out] ref NativeMethods.MSG msg, IntPtr hWnd, int uMsgFilterMin, int uMsgFilterMax);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern short RegisterClass(NativeMethods.WNDCLASS wc);
      }

      #endregion
    }

现在这是我的应用代码,所有这些都是一个应用的一部分

    public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }

            [DllImport("PaymentManager.dll")]
            static extern int openpaymentmanager();

            [DllImport("PaymentManager.dll")]
            static extern int closepaymentmanager();

            [DllImport("PaymentManager.dll")]
            static extern int startpaymentmanager(IntPtr handle,int messageaddress,int devices,int protocol,int messageconfig);

            [DllImport("PaymentManager.dll")]
            static extern int stoppaymentmanager();

            [DllImport("PaymentManager.dll")]
            static extern int setpaymentmanager(int command, int selection, int info1, int info2);


            private void Form1_Load(object sender, EventArgs e)
            {
                int closepaymentmng = closepaymentmanager();

                const int WM_USER = 0x0400;
                MessageMonitor.MessageReceived += new MessageMonitorEventHandler(MessageMonitor_MessageReceived);
                MessageMonitor.WatchMessage(0x3B9); // MM_MCINOTIFY
                int openpaymentmng = openpaymentmanager();
                int startpaymentmnh = startpaymentmanager(MessageMonitor.WindowHandle , 0x3B9, 0, 0, 0);
                long setpaymentmng = setpaymentmanager(0, 0, 0, 0);
                textBoxprice.Text = "0";
                textBoxpaid.Text = "0";
            }



            private void MessageMonitor_MessageReceived(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam)
            {

                Console.WriteLine(string.Format ("message:{0} , wparam= {1} ,lparam = {2}", message,wParam,lParam));
                if (wParam.ToString() == "17")
                {
                    textBoxpaid.Text = (Convert.ToDecimal( textBoxpaid.Text)+ Convert.ToDecimal( lParam.ToString())/100).ToString();
                }
                if (wParam.ToString() == "18")
                {
                    textBoxpaid.Text = (Convert.ToDecimal(textBoxpaid.Text) + Convert.ToDecimal(lParam.ToString()) / 100).ToString();
                }
                if (wParam.ToString() == "33")
                {
                    textBoxpaid.Text = (Convert.ToDecimal(textBoxpaid.Text) - Convert.ToDecimal(lParam.ToString()) / 100).ToString();
                }
            }
     ect.....

我尝试过以下但未使用

     public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
            private const string crashDllName = @"C:\Users\Chantelle\Documents\CashRecyclerTestapp\CashRecyclerTestapp\bin\Debug\PaymentManager.dll";

            [DllImport(crashDllName)]
            static extern int openpaymentmanager();

            [DllImport(crashDllName)]
            static extern int closepaymentmanager();

            [DllImport(crashDllName)]
            static extern int startpaymentmanager(IntPtr handle,int messageaddress,int devices,int protocol,int messageconfig);

            [DllImport(crashDllName)]
            static extern int stoppaymentmanager();

            [DllImport(crashDllName)]
            static extern int setpaymentmanager(int command, int selection, int info1, int info2);


            private static  IntPtr hndle;
            private static MessageMonitorEventHandler msgmntr;

            private void Form1_Load(object sender, EventArgs e)
            {
                int closepaymentmng = closepaymentmanager();

                const int WM_USER = 0x0400;
                msgmntr = new MessageMonitorEventHandler(MessageMonitor_MessageReceived);
                MessageMonitor.MessageReceived +=  msgmntr;
                MessageMonitor.WatchMessage(0x3B9); // MM_MCINOTIFY
                hndle = MessageMonitor.WindowHandle;

                int openpaymentmng = openpaymentmanager();
                int startpaymentmnh = startpaymentmanager(hndle, 0x3B9, 0, 0, 0);
                long setpaymentmng = setpaymentmanager(0, 0, 0, 0);
                textBoxprice.Text = "0";
                textBoxpaid.Text = "0";
            }



            public  void MessageMonitor_MessageReceived(IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam)
            {

                Console.WriteLine(string.Format ("message:{0} , wparam= {1} ,lparam = {2}", message,wParam,lParam));
                if (wParam.ToString() == "17")
                {
                    textBoxpaid.Text = (Convert.ToDecimal( textBoxpaid.Text)+ Convert.ToDecimal( lParam.ToString())/100).ToString();
                }
                if (wParam.ToString() == "18")
                {
                    textBoxpaid.Text = (Convert.ToDecimal(textBoxpaid.Text) + Convert.ToDecimal(lParam.ToString()) / 100).ToString();
                }
                if (wParam.ToString() == "33")
                {
                    textBoxpaid.Text = (Convert.ToDecimal(textBoxpaid.Text) - Convert.ToDecimal(lParam.ToString()) / 100).ToString();
                }
            }
已尝试其他方式没有成功。所以我真的需要任何帮助,即使它以另一种方式捕捉消息,但我已经超出了沮丧并且会感激任何帮助

1 个答案:

答案 0 :(得分:12)

RegisterWndClass中,您要创建一个本地WNDCLASS实例 - wclpfnWndProc字段包含一个用于回调代码的代理。由于这是一个局部变量,因此只要该方法完成,wc就有资格进行垃圾回收,正如用于回调代码的委托一样。

您必须确保您作为回调传递的委托不是垃圾回收。这可以通过向MessageMonitor类添加静态字段来实现:

private static readonly NativeMethods.WndProc StaticWndProcDelegate = WndProc;

在您目前拥有的RegisterWndClass中:

wc.lpfnWndProc = WndProc;

将其替换为:

wc.lpfnWndProc = StaticWndProcDelegate;