2009-12-22 6 views
4

J'ai un objet géré dans un fichier C# dll qui gère un descripteur d'entier anonyme pour un objet non géré dans une DLL C++. À l'intérieur de la DLL C++, l'entier anonyme est utilisé dans un fichier std :: map pour récupérer un objet C++ non géré. Grâce à ce mécanisme, je peux maintenir une association libre entre un objet géré et non géré à l'aide d'un descripteur d'entier anonyme.Gestion des destructeurs d'objets gérés (C#) et non gérés (C++)

Dans la méthode finalize (destructeur) de l'objet géré, j'ai un appel dans la DLL non managée pour supprimer l'objet non géré.

Tout va bien car le programme C# s'exécute, mais j'ai un problème quand le programme se termine. Étant donné que je n'ai aucun contrôle sur l'ordre des opérations de suppression du côté géré, la DLL non managée est supprimée de la mémoire AVANT tout objet géré. Ainsi, lorsque le destructeur de l'objet géré est appelé (qui à son tour appelle le destructeur non géré [au moins indirectement]), l'objet non géré a déjà été supprimé et le programme se bloque.

Comment puis-je supprimer en toute sécurité un objet non géré dans une DLL C++ externe associée à un objet géré dans un programme C#.

Merci

Andrew

Répondre

3

Vous pouvez résoudre ce problème rapidement en cochant Environment.HasShutdownStarted dans le finaliser de votre objet C# (sans appeler la DLL C++/en supprimant l'objet C++ si la propriété HasShutdownStarted a la valeur true). Si vous n'êtes pas dans l'AppDomain principal, vous devrez peut-être vérifier AppDomain.Current.IsFinalizingForUnload à la place (en fait, cela peut être plus sûr en général). Notez que cela évite simplement d'appeler la bibliothèque libérée (c'est-à-dire évite d'exécuter le destructeur non géré): si la bibliothèque non gérée contenait une ressource qui ne sera pas automatiquement libérée lors de l'arrêt du processus, cette ressource pourrait être divulguée. (La plupart des ressources du système d'exploitation sont libérées lors de l'arrêt du processus, ce qui ne pose généralement pas de problème.Et comme le fait remarquer Adam, le finaliseur CLR est conçu comme un dispositif de sécurité: vous voulez vraiment libérer les ressources de façon plus déterministe. Par conséquent, si structurellement possible, la suggestion d'Igor d'implémenter IDisposable sur la classe C# et d'éliminer de façon déterministe l'objet serait préférable.

1

Vous devez supprimer votre objet non géré à partir de la méthode Dipose de votre objet géré. Vous devez également appeler Dispose à partir de la méthode Finalize au cas où votre code n'a pas appelé Dispose avant que le garbage collector ne s'y connecte. La réponse d'Adam Robinson illustre cela beaucoup mieux. Donc, si vous êtes dilligent avec vous Éliminer les appels (et utiliser les blocs using), vous ne devriez pas avoir des plantages d'arrêt.

Modifier: Je pense que le problème est en fait la DLL non managée est déchargée avant l'exécution du finaliseur. Vous êtes vieux "Une fois que l'application est en cours de fermeture, il n'y a aucune garantie quant à l'ordre dans lequel ils sont déchargés".

Peut-être pouvez-vous tester vos ressources non gérées dans un assemblage C++ géré? De cette façon, vous savez que la DLL ne va pas exploser avant que vous n'en ayez fini et que vous n'ayez pas à faire de vilaines choses P/Invoke.

Voici un exemple de MSDN:

ref struct A { 
    // destructor cleans up all resources 
    ~A() { 
     // clean up code to release managed resource 
     // ... 
     // to avoid code duplication 
     // call finalizer to release unmanaged resources 
     this->!A(); 
    } 

    // finalizer cleans up unmanaged resources 
    // destructor or garbage collector will 
    // clean up managed resources 
    !A() { 
     // clean up code to release unmanaged resource 
     // ... 
    } 
}; 

Plus ici http://msdn.microsoft.com/en-us/library/ms177197.aspx

Ce qui précède est le même schéma que celui C#, sauf que vous pourriez sortir d'avoir les ressources unamanaged dans l'ensemble C++ géré . Si vous devez vraiment avoir ceux dans une DLL non managée (pas une bibliothèque statique non gérée) alors vous êtes bloqué, vous aurez les mêmes problèmes d'arrêt.

8

Le finaliseur de n'importe quel objet géré doit presque toujours être utilisé uniquement comme sécurité intrinsèque. En règle générale, si vous avez une logique de finalisation, votre objet doit probablement implémenter IDisposable. Le modèle de base pour la mise en œuvre IDisposable est (disons que le nom de la classe est MyClass):

public class MyClass : IDisposable 
{ 
    private int extHandle; 

    public MyClass() 
    { 
     extHandle = // get the handle 
    } 

    public void Dispose() 
    { 
     Dispose(true); 

     GC.SuppressFinalize(this); 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     if(disposing) 
     { 
      // call dispose() on any managed objects you might have 
     } 

     // release the handle 
    } 

    ~MyClass() 
    { 
     Dispose(false); 
    } 
} 

Cela signifie aussi que quel que soit le code crée et utilise cet objet doit être en mesure de gérer la durée de vie de l'objet. La façon la plus simple est d'enfermer l'instance dans un bloc using, comme ceci:

using(MyClass c = new MyClass()) 
{ 
    // do things with c 
} 

Le bloc using appelle automatiquement Dispose sur l'objet qui tombe hors de portée à la fin du bloc. Les choses, bien sûr, se compliquent lorsque l'objet doit exister en dehors d'une seule fonction. Dans tous les cas, chaque fois que l'objet est terminé avec Dispose doit être appelé.

+0

Le véritable problème est le couplage très lâche. Avec juste une poignée, l'application ne pense pas qu'il y a des dépendances et en tant que telle, elle peut décharger dans n'importe quel ordre. Une chose qui pourrait l'aider à établir au moins une référence forte qui devrait s'assurer que la DLL managée sera déchargée avant le non managé. Cela n'a pas besoin d'être la référence que vous utilisez actuellement, juste une référence forte devrait être bonne. J'ai développé un système avec des milliers de références à DLL non-mm en utilisant C++ géré et n'ai pas rencontré ce problème. Le C++ géré pourrait également être une solution possible. –

+0

@Jim: Suivre le modèle 'IDisposable' recommandé devrait résoudre ce problème, car les objets seront (ou devraient) être éliminés avant le début du déchargement. –

1

La manière habituelle de faire est de tirer votre objet géré de IDisposable

J'essaie toujours d'appeler explicitement object.Dispose quand je suis fait avec l'objet, mais je ne suis pas sûr que ce serait nécessaire dans votre Cas. La documentation que j'ai lue n'est pas claire quant à savoir si elle garantit que Dispose() sera appelé avant que votre DLL ne soit déchargée ou non.

Dans mon propre code, le domaine de code managé est explicitement supprimé avant la fermeture de l'application non gérée. Je n'ai donc pas à m'inquiéter de ce problème particulier.

+2

Non, il n'y a aucune garantie que 'Dispose' sera appelé (car le GC lui-même ne fait rien de spécial pour les objets qui implémentent cette interface). C'est pourquoi la logique de finalisation que j'ai démontrée est un modèle fortement recommandé. –

+1

Dispose ne sera * pas * appelé avant que votre DLL ne soit déchargée, à moins que quelque chose ne l'appelle; c'est-à-dire que le CLR n'appellera pas * Dispose for you. (Certains composants .NET, tels que Form, l'appellent pour leurs enfants.) Le CLR * appellera Finalize() lorsqu'un objet est collecté, y compris pendant l'arrêt. Mais Dispose() est purement une chose de code d'utilisateur. – itowlson

+0

+1 à itowlson; Exactement, le modèle prévu est que tout ce qui implémente 'IDisposable' devrait appeler' Dispose' sur tous les objets membres qu'il gère qui implémentent également 'IDisposable'. –

Questions connexes