Question

Problème (simplifié pour clarifier les choses):

    1. il y a un static.lib lié statiquement qui a une fonction qui incrémente:
    
        extern int CallCount = 0;
        int TheFunction()
        {
            void *p = &CallCount;
            printf("Function called");
            return CallCount++;
        }
    
    2. static.lib est lié à un C ++ / CLI managé managed.dll qui englobe la méthode TheFunction:
    
        int Managed::CallLibFunc()
        {
            return TheFunction();
        }
    
    3. L'application test a une référence à managed.dll et crée plusieurs domaines qui appellent l'encapsuleur C ++ / CLI:
    
        static void Main(string[] args)
        {
            Managed c1 = new Managed();
            int val1 = c1.CallLibFunc();
            // value is zero
    
            AppDomain ad = AppDomain.CreateDomain("NewDomain");
            Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
            int val2 = c.CallLibFunc();
            // value is one 
        }
    

Question:

D'après ce que j'ai lu dans Essential .NET Vol1 The CLR de Don Box, je m'attendrais à ce que val2 soit égal à zéro car une nouvelle copie de managed.dll / static.lib est chargée lorsque CreateInstanceAndUnwrap est appelée. Est-ce que je comprends mal ce qui se passe? La bibliothèque statique ne semble pas respecter les limites de l'application, car il s'agit d'un code non géré. Existe-t-il un moyen de contourner ce problème autrement qu'en créant un nouveau processus d'instanciation de Managed?

Merci beaucoup à tous!

Était-ce utile?

La solution

Mon impression était que, comme vous vous en doutiez, les DLL non gérées sont chargées dans le contexte du processus et non dans celui d'AppDomain. Ainsi, toutes les données statiques contenues dans du code non géré sont partagées entre AppDomains.

Ce lien montre une personne qui a le même problème que vous ne l'avez toujours pas vérifié à 100%, mais c'est probablement le cas.

Ce lien concerne la création d'un rappel depuis code non géré dans un AppDomain en utilisant une astuce thunking. Je ne suis pas sûr que cela puisse vous aider, mais vous trouverez peut-être cela utile pour créer une solution de contournement.

Autres conseils

Après avoir appelé

Managed c1 = new Managed(); 

Votre wrapper managed.dll sera chargé dans le domaine d'application principal de votre application. Jusqu'à ce qu'il y ait des domaines non gérés, les données de static.lib seront partagées avec d'autres domaines. Au lieu de créer un processus séparé, vous devez simplement vous assurer (avant chaque appel) que managed.dll n'est chargé dans aucun domaine d'application.

Comparez avec cela

static void Main(string[] args)
{

    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  Value is zero

               AppDomain.Unload(ad)
    }
    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  I think value is zero

               AppDomain.Unload(ad)
    }


}
`

IMPORTANT et: Si vous n’ajoutez qu’une seule ligne, le compilateur JIT chargera managed.dll et la magie disparaîtra.

static void Main(string[] args)
{

    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  Value is zero 

               AppDomain.Unload(ad)
    }
    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  I think value is one

               AppDomain.Unload(ad)
    }
Managed c1 = new Managed(); 


}

Si vous ne voulez pas dépendre de telles lignes, vous pouvez créer un autre wrapper, ManagedIsolated.dll, qui référencera managed.dll et effectuera chaque appel dans un domaine distinct avec le déchargement du domaine juste après l'appel. L’application principale ne dépend que des types ManagedIsolated.dll et Managed.dll ne sera pas chargé dans le domaine de l’application principale.

Cela ressemble à une astuce mais peut-être sera-t-il utile pour quelqu'un.     `

En bref, peut-être. AppDomains sont purement un concept géré. Lorsqu'une instance de domaine d'application est instanciée, elle ne mappe pas dans les nouvelles copies des DLL sous-jacentes, elle peut réutiliser le code déjà en mémoire (par exemple, vous ne vous attendriez pas à ce qu'elle charge de nouvelles copies de tous les assemblys System. *, À droite ?)

Dans le monde géré, toutes les variables statiques sont définies par AppDomain, mais comme vous le signalez, cela ne s'applique pas au monde non géré.

Vous pouvez faire quelque chose de complexe qui oblige le chargement d'un unique managed.dll pour chaque domaine d'application, ce qui entraînerait l'installation d'une nouvelle version de la bibliothèque statique. Par exemple, utiliser Assembly.Load avec un tableau d'octets pourrait fonctionner, mais je ne sais pas comment le CLR tentera de gérer la collision dans les types si le même assemblage est chargé deux fois.

Je ne pense pas que nous en arrivions au problème réel - voir cet article de la DDJ .

La valeur par défaut de l'attribut d'optimisation du chargeur est SingleDomain, ce qui "force l'AppDomain à charger une copie privée du code de chaque ensemble nécessaire". Même s’il s’agissait d’une des valeurs de plusieurs domaines ", chaque domaine d’application conserve toujours une copie distincte des champs statiques".

'managed.dll' est (comme son nom l'indique) un assemblage géré. Le code de static.lib a été compilé de manière statique (sous forme de code IL) en 'managed.dll', je m'attendrais donc au même comportement que celui attendu par Lenik ....

... sauf si static.lib est une bibliothèque d'exportation statique pour une DLL non gérée. Lenik dit que ce n'est pas le cas, donc je ne suis toujours pas sûr de ce qui se passe ici.

Avez-vous essayé d'exécuter des processus distincts? Une bibliothèque statique ne doit pas partager des instances de mémoire en dehors de son propre processus.

Cela peut être difficile à gérer, je sais. Je ne sais pas quelles seraient vos autres options dans ce cas cependant.

Edit: Après avoir regardé un peu autour, je pense que vous pouvez faire tout ce dont vous avez besoin avec System.Diagnostics.Process . À ce stade, vous disposez de nombreuses options pour la communication, mais .NET Remoting ou WCF seraient probablement des choix faciles et efficaces.

Ce sont les deux meilleurs articles que j'ai trouvés sur le sujet

La partie importante est:

  

Les champs statiques basés sur RVA sont des processus globaux. Celles-ci sont limitées aux scalaires et aux types de valeur, car nous ne voulons pas permettre aux objets de se fondre au-delà des limites d'AppDomain. Cela causerait toutes sortes de problèmes, notamment lors des déchargements d'AppDomain. Certains langages tels que ILASM et MC ++ facilitent la définition de champs statiques basés sur RVA. La plupart des langues ne le font pas.

Ok, donc si vous contrôlez le code dans la librairie, j'essaierais

class CallCountHolder {
   public:
     CallCountHolder(int i) : count(i) {}
     int count;
};

static CallCountHolder cc(0);
int TheFunction()
{
    printf("Function called");
    return cc.count++;
}

Depuis qu'il a dit que les champs statiques basés sur RVA sont limités aux scalaires et aux types de valeur. Un tableau int peut également fonctionner.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top