2011-05-24 2 views
0

J'ai développé une application C# qui utilise DLL interop à une application de base de données externe.C# -Mon application et interopérabilité (DLL/COM) avec une application externe

Cette application externe démarre en même temps que mon application C# et est disponible tant que mon application C# est en cours d'exécution.

Maintenant, la vraie question est liée à la gestion des objets que j'ai besoin de créer pour interagir avec l'application externe. Lorsque je déclare des objets disponibles à partir des DLL référencées, ces objets ont des méthodes qui fonctionnent avec des fichiers (propriétaires) et exécutent des requêtes (comme si elles l'avaient été avec cette interface utilisateur externe). Ces objets sont détruits « par moi » en utilisant Marshal.ReleaseComObject(A_OBJECT) tandis que d'autres fonctionnent dans un domaine d'application diferent, en utilisant AppDomain.CreateDomain("A_DOMAIN"), effectuer les opérations et appeler un AppDomain.Unload("A_DOMAIN"), libérant les DLL utilisées pour l'opération ...

Ces solutions de contournement sont faits pour assurez-vous que cette application externe ne "bloque" pas les fichiers utilisés dans ces opérations, permettant ainsi leur suppression ou leur déplacement depuis un dossier.

par exemple.

private static ClientClass objApp = new ClientClass(); 

public bool ImportDelimitedFile(
       string fileToImport, 
       string outputFile, 
       string rdfFile)  

{ 
    GENERICIMPORTLib import = new GENERICIMPORTLibClass(); 

    try 
    { 
     import.ImportDelimFile(fileToImport, outputFile, 0, "", rdfFile, 0); 
     return true; 
    } 
    catch (Exception ex) 
    { 
     MessageBox.Show(ex.Message); 
     return false; 
    } 
    finally 
    { 
     System.Runtime.InteropServices.Marshal.ReleaseComObject(import); 
     import = null; 
    } 
} 

public int DbNumRecs(string file) 
{ 
    if (!File.Exists(file)) 
    { 
     return -1; 
    } 

    System.AppDomain newDomain = System.AppDomain.CreateDomain(); 
    COMMONIDEACONTROLSLib db = new COMMONIDEACONTROLSLibClass(); 
    try 
    { 
     db = objApp.OpenDatabase(file); 
     int count = (int)db.Count; 

     db.Close(); 
     objApp.CloseDatabase(file); 

     return count; 
    } 
    catch (Exception ex) 
    { 
     return -1; 
    } 
    finally 
    { 
     System.AppDomain.Unload(newDomain); 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
    } 
} 

Ces deux «solutions» ont été obtenues par essais et erreurs, étant donné que je ne possède aucun type de manuel API. Ces solutions sont-elles correctes? Pouvez-vous m'expliquer les différences? Ai-je vraiment besoin de travailler avec les deux solutions ou devrait-on suffire?

Merci!

Répondre

1

Votre utilisation de AppDomains est incorrecte. Ce n'est pas parce que vous créez un nouvel AppDomain avant la ligne X que la ligne X s'exécute réellement dans ce domaine d'application.

Vous devez rassembler une classe proxy sur votre AppDomain et l'utiliser dans la classe actuelle.

public sealed class DatabaseProxy : MarshallByRefObject 
{ 
    public int NumberOfRecords() 
    {  
     COMMONIDEACONTROLSLib db = new COMMONIDEACONTROLSLibClass(); 
     try 
     { 
      db = objApp.OpenDatabase(file); 
      int count = (int)db.Count; 

      db.Close(); 
      objApp.CloseDatabase(file); 

      return count; 
     } 
     catch (Exception ex) 
     { 
      return -1; 
     } 
    } 
} 

et

public int NumberOfRecords() 
{  

    System.AppDomain newDomain = null; 

    try 
    { 
     newDomain = System.AppDomain.CreateDomain(); 
     var proxy = newDomain.CreateInstanceAndUnwrap(
            typeof(DatabaseProxy).Assembly.FullName, 
            typeof(DatabaseProxy).FullName); 
     return proxy.NumberOfRecords(); 
    } 
    finally 
    { 
     System.AppDomain.Unload(newDomain); 
    } 
} 

Vous pouvez effectivement créer un marshall retour l'objet COM lui-même au lieu de l'instanciation via votre proxy. Ce code est complètement écrit ici et non testé, donc peut-être bogué.

+0

Merci, je vais essayer la solution postée par @James, je pense que c'est plus propre pour mon but. Merci – Libas

1

La première solution est la meilleure. COM non géré utilise un schéma de comptage de référence; IUnknown est l'interface de comptage de référence sous-jacente: http://msdn.microsoft.com/en-us/library/ms680509(VS.85).aspx. Lorsque le compte de référence atteint zéro, il est libéré.

Lorsque vous créez un objet COM dans .NET, un encapsuleur est créé autour de l'objet COM. L'encapsuleur maintient un pointeur sur l'IUnknown sous-jacent. Lorsque le garbage collection se produit, le wrapper appelle la fonction IUnknown :: Release() sous-jacente pour libérer l'objet COM lors de la finalisation. Comme vous l'avez remarqué, le problème est que parfois l'objet COM verrouille certaines ressources critiques. En appelant Marshal.ReleaseComObject, vous forcez un appel immédiat à IUnknown :: Release sans avoir besoin d'attendre (ou d'initier) un garbage collection général. Si aucune autre référence à l'objet COM n'est conservée, il sera immédiatement libéré. Bien sûr, l'encapsuleur .NET devient invalide après ce point.

La deuxième solution fonctionne apparemment en raison de l'appel à GC.Collect(). La solution est plus maladroite, plus lente et moins fiable (l'objet COM n'est peut-être pas nécessairement collecté: le comportement dépend de la version spécifique de .NET Framework).L'utilisation de AppDomain n'apporte rien car votre code ne fait rien d'autre que créer un domaine vide et le décharger. AppDomains sont utiles pour isoler les assemblys .NET Framework chargés. Parce que le code COM non géré est impliqué, AppDomains ne sera pas vraiment utile (si vous avez besoin d'isolation, utilisez l'isolation de processus). La deuxième fonction peut probablement être réécrite comme:

public int DbNumRecs(string file) { 
     if (!File.Exists(file)) { 
      return -1; 
     } 
     // don't need to use AppDomain 
     COMMONIDEACONTROLSLib db = null; // don't need to initialize class here 
     try { 
      db = objApp.OpenDatabase(file); 
      return (int)db.Count; 
     } catch (Exception) } // don't need to declare unused ex variable 
      return -1; 
     } finally { 
      try { 
       if (db != null) { 
        db.Close(); 
        Marshal.ReleaseComObject(db); 
       } 
       objApp.CloseDatabase(file); // is this line really needed? 
      } catch (Exception) {} // silently ignore exceptions when closing 
     } 
    } 
+0

Merci pour le conseil! J'ai beaucoup lu sur le RCW et les mécanismes du système de comptage des références COM, mais pour être sincère, je ne savais pas que c'était vraiment la meilleure solution ... Je vais changer toutes mes méthodes qui fonctionnent avec cet extern app, testez-le et portez les résultats ici! Merci encore! – Libas

+0

Vous avez une autre question, l'ordre de sortie est important, non? Imaginez ce cas: db = objApp.OpenDatabase (fichier); table = (COMDBLib) db.TableDef(); Je devrais dans la clause finale: d'abord le Marshal.ReleaseComObject (table); puis le Marshal.ReleaseComObject (db); Je demande cela en raison du fait que le compteur de référence à "table" devrait en théorie dépendre de la référence à "db", en raison du fait que "table" a été initialisé par "db" ... Droite? Merci. – Libas

+0

Cela peut être important ou non selon la bibliothèque. Par exemple, si l'implémentation "table" contient en interne une référence à la variable "db", alors "db" ne sera pas libéré avant que vous et "table" ne le libèrent. D'un point de vue pratique cependant, je le fais généralement comme vous le suggérez et je ne m'inquiète pas. –

Questions connexes