2009-11-30 3 views
1

Я работаю с USB-устройством. Это устройство получает сообщения, и я не знаю, когда и как часто. API, который поставляется с драйвером, указывает функцию setreceiveCallBack, которая дает обратный вызов, когда устройство получает сообщение. Но в случайные моменты или промежутки времени я получаю обратный вызов на garbagecollected delegate exeption. Я искал решения для своей проблемы, но ни одно из решений, похоже, не работает в моем случае. Ниже большая часть моего кода:CallBack on garbagecollected Delegate

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.IO; 
using System.Linq; 
using System.Text; 
using System.Runtime.InteropServices; 
using System.Windows.Forms; 

namespace CallBacktesting 
{ 
    public unsafe delegate void callBack(Form1.CANMsg *pmsg); 

    public partial class Form1 : Form 
    { 
     uint handle; 
     static WriteLog log = new WriteLog(); 
     Boolean getCan = false; 
     static int frameCount = 0; 
     static CANMsg newmsg = new CANMsg(); 
     callBack _setCallBack; 
     List<string> write = new List<string>(); 

     public Form1() 
     { 
      InitializeComponent(); 
     } 


     private void buttonOpen_Click(object sender, EventArgs e) 
     { 
       // Open connection 
     } 

     private void buttonClose_Click(object sender, EventArgs e) 
     { 
       // Close connection 
     } 

     private void buttonCallBack_Click(object sender, EventArgs e) 
     { 
      if (!getCan) 
      { 
       int rv; 
       unsafe 
       { 
        callBack _setCallBack = new callBack(call); 
        rv = canusb_setReceiveCallBack(handle, _setCallBack); 
       } 
       label1.Text = rv.ToString(); 
      } 
      else 
      { 
       _setCallBack = null; 
       int rv = canusb_setReceiveCallBack(handle, _setCallBack); 
       GC.KeepAlive(_setCallBack); 
       label1.Text = rv.ToString(); 
      } 
     } 

     public unsafe void call(CANMsg *pmsg) 
     { 
      newmsg = *pmsg; 
      update(); 
     } 

     private void buttonExit_Click(object sender, EventArgs e) 
     { 
      GC.KeepAlive(_setCallBack); 
      Application.Exit(); 
     } 

     [DllImport("canusbdrv.dll", EntryPoint = "canusb_setReceiveCallBack")] 
     public static extern int canusb_setReceiveCallBack(uint handle, callBack callBack); 

     unsafe private void timer_Tick(object sender, EventArgs e) 
     { 
       // update the form with received messages 
     } 

     public void update() 
     { 
      CANMsg msgrec = newmsg; 
      // Build str from messages with all data 
      write.Add(str); 
      log.logWrite(str); 
      frameCount++; 
     } 
    } 

    public class WriteLog 
    { 

     private void OpenFile() 
     {  } 

     public void logWrite(string log) 
     {  } 

     public void logAdd(string log) 
     {  } 

     private void logClose() 
     {  } 
    } 
} 
+0

Я удаляю некоторый код для удобочитаемости и исправил ошибку (используется _setCallBack вместо setCallBack) –

ответ

2

в вашем коде, когда вы делаете



       callBack setCallBack = new callBack(call); 
       rv = canusb_setReceiveCallBack(handle, call); 

делегат будет доступен для сбора мусора после вызова «canusb_setReceiveCallBack», потому что нет, где в коде не является делегатом ссылки.

Вы можете избежать этого, чтобы хранить его в закрытом поле.

E.x .:


Class Form1 
{ 

callBack _setCallBack; 

private void buttonCallBack_Click(object sender, EventArgs e) 
{ 


       _setCallBack = new callBack(call); 
       rv = canusb_setReceiveCallBack(handle, _setCallBack); 

} 

} 

Но это может иметь некоторые проблемы, потому что каждый щелчок кнопки создаст новый обратный вызов. Это может быть проблематично, если предыдущий обратный вызов необходимо указать.

Я думаю, что вам нужно сделать рефакторинг кода, чтобы использовать SafeHandle для хранения дескриптора, возвращаемого canusb_Open.

Я бы разработал этот класс.


class CanUsbSafeHandle : SafeHandle 
{ 
    private EventHandler _receiveCallBack; 
    private readonly object _receiveCallBackLock = new object(); 

    public event EventHandler ReceiveCallBack 
    { 
     add 
     { 
      lock (_receiveCallBackLock) 
      { 
       bool hasListeners = (_receiveCallBack != null); 
       _receiveCallBack += value; 
       //call canusb_setReceiveCallBack only when 1 or more listeners were added 
       //and there were previously no listeners 
       if (!hasListeners && (_receiveCallBack != null)) 
       { 
        canusb_setReceiveCallBack(this, setCallBack); 
       } 
      } 
     } 
     remove 
     { 
      lock (_receiveCallBackLock) 
      { 
       bool hasListeners = (_receiveCallBack != null); 
       _receiveCallBack -= value; 
       //call canusb_setReceiveCallBack only when there are no more listeners. 
       if(hasListeners && (_receiveCallBack == null)) 
       { 
        canusb_setReceiveCallBack(this, null); 
       } 
      } 
     } 
    } 

    public CanUsbSafeHandle() 
     : base(IntPtr.Zero, true) 
    { 
    } 

    public override bool IsInvalid 
    { 
     get { return handle == IntPtr.Zero; } 
    } 

    protected override bool ReleaseHandle() 
    { 
     return canusb_Close(handle); 
    } 

    protected override void Dispose(bool disposing) 
    { 
     if (disposing) 
     { 
      lock (_receiveCallBackLock) 
      { 
       _receiveCallBack = null; 
      } 
     } 
     base.Dispose(disposing); 
    } 
} 

Таким образом, то SafeHandle будет управлять время жизни «получить обратный вызов» делегата будет управляться SafeHandle.

+0

Краген указал на то, что я пропустил, и это значит, что вместо «setCallBack» передается «вызов». –

+0

oke Я скорректировал setCallBack для частного поля и восстановил _setCallBack, передаваемый вместо вызова. Поскольку программа должна использовать только 1 CallBack, я пока не пробовал ваше предложение Safehandle (и я не уверен, что я действительно это понимаю). собирается протестировать его сейчас –

+0

Большое спасибо. Кажется, что так же просто, как частное поле. –

2

Является ли это правильно/опечатка ?:

callBack setCallBack = new callBack(call); 
rv = canusb_setReceiveCallBack(handle, call); 

Вы появляетесь создать экземпляр CallBack, но затем передать что-то еще canusb_setReceiveCallBack - вы имели в виду передать setCallBack вместо этого?

Кроме того, на этой линии вы объявляете setCallBack быть локальной переменной, и поэтому даже если вы пройти setCallBack вместо call, вы все еще пропусканием локальную область действия переменной, которая, вероятно, будет собранный мусор (я заметил, что вы сделать GC.KeepAlive(setCallBack); явно предотвратить это)

+0

Я пробовал оба. Я не был уверен, что мне нужно было пройти setCallBack или передать сам funtion. Но, похоже, это не имеет никакого значения, оба дают исключение. Я также объявил setCallBack в начале, чтобы сделать его живым во время жизни моего приложения. –