2009-11-30 7 views
1

Je travaille avec un périphérique USB. Cet appareil reçoit des messages et je ne sais pas quand et à quelle fréquence. L'API fournie avec le pilote spécifie une fonction setreceiveCallBack qui fournit un rappel lorsque le périphérique reçoit un message. Mais à des moments ou des intervalles aléatoires, je reçois un rappel sur l'exeption du délégué garbagecollected. J'ai cherché des solutions à mon problème mais aucune des solutions ne semble fonctionner dans mon cas. Ce qui suit est la plus grande partie de mon code:CallBack on garbagecollected Délégué

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

Je supprime du code pour la lisibilité et corrige l'erreur (utilisé _setCallBack au lieu de setCallBack) –

Répondre

2

dans votre code, lorsque vous faites



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

le délégué sera disponible pour la collecte des ordures après que vous invoquez « canusb_setReceiveCallBack » parce que nulle part dans votre code est le délégué référencé.

Vous pourriez éviter ce soit le stocker dans un domaine privé.

E.x .:


Class Form1 
{ 

callBack _setCallBack; 

private void buttonCallBack_Click(object sender, EventArgs e) 
{ 


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

} 

} 

Mais cela peut avoir des problèmes parce que chaque bouton clic créer un nouveau rappel. Cela peut être problématique si le rappel précédent doit être référencé.

Je pense que ce que vous devez faire est de refactoriser le code pour utiliser un SafeHandle pour stocker le handle renvoyé par canusb_Open.

Je voudrais concevoir la classe comme ceci.


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

De cette façon, le SafeHandle va gérer la durée de vie du délégué « recevoir rappel » sera géré par le SafeHandle.

+0

Kragen a fait remarquer quelque chose qui me manquait, et c'est que 'call' est passé à la place de 'setCallBack'. –

+0

oke J'ai ajusté setCallBack à un champ privé et restauré le _setCallBack étant passé au lieu de l'appel. Parce que le programme ne devrait utiliser que 1 CallBack, je n'essaie pas votre suggestion de Safehandle pour l'instant (et je ne suis pas sûr de le comprendre vraiment). va le tester maintenant –

+0

Merci beaucoup. Tout aussi simple qu'un domaine privé semble fonctionner. –

2

Est-ce exact/une faute de frappe ?:

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

Vous semblez créer une instance de callBack, mais passe alors quelque chose d'autre à canusb_setReceiveCallBack - vouliez-vous passer à setCallBack à la place?

En outre, sur cette ligne, vous déclarez setCallback à une variable locale, et même si vous ne passez setCallBack au lieu de call, vous passez encore une variable SCOPED locale qui sera probablement ramasse-miettes (j'ai remarqué que vous ne GC.KeepAlive(setCallBack); pour empêcher explicitement)

+0

J'ai essayé les deux. Je n'étais pas sûr si je devais passer setCallBack ou pourrait passer la fonction elle-même. Mais cela n'a pas semblé faire la différence, les deux donnent l'exception. J'ai également déclaré setCallBack au début pour le rendre vivant pendant la durée de vie de mon application. –