Utilisation de ptr partagé dans les interfaces dll

j'ai une classe abstraite dans ma dll.

class IBase {
  protected:
       virtual ~IBase() = 0;
  public:
       virtual void f() = 0;
};

je veux avoir IBase dans mon fichier exe qui charge dll. La première façon est de créer la fonction suivante

IBase * CreateInterface();

et d'ajouter la fonction virtuelle Release() dans IBase .

Deuxième façon est de créer une autre fonction

boost::shared_ptr<IBase> CreateInterface();
La fonction

et non Release() est nécessaire.

Question.

1) est-il vrai que le destructeur et la désallocation de la mémoire sont appelés dans la dll (pas dans exe-file) dans le second cas ?

2) Ne le deuxième cas fonctionne bien si exe-fichier et dll a été compilé avec des compilateurs différents (ou des cadres différents).

21
demandé sur Alexey Malistov 2009-10-22 11:56:55

4 réponses

une réponse à votre première question: le destructeur virtuel dans votre dll est appelé - les informations sur son emplacement est incorporé dans votre objet (dans le vtable). Dans le cas de libération mémoire cela dépend de la façon disciplinée les utilisateurs de votre IBase . S'ils savent qu'ils doivent appeler Release() et considèrent que l'exception peut contourner le flux de commande dans une direction surprenante, la bonne sera utilisée.

mais si CreateInterface() retourne shared_ptr<IBase> il peut lier la fonction de désallocation droite à ce pointeur intelligent. Votre bibliothèque peut ressembler à ceci:

Destroy(IBase* p) {
    ... // whatever is needed to delete your object in the right way    
}

boost::shared_ptr<IBase> CreateInterface() {
    IBase *p = new MyConcreteBase(...);
    ...
    return shared_ptr<IBase>(p, Destroy); // bind Destroy() to the shared_ptr
}                                         // which is called instead of a plain
                                          // delete

ainsi chaque utilisateur de votre DLL est facilement empêché contre les fuites de ressources. Ils n'ont jamais à se soucier d'appeler Release() ou de faire attention aux exceptions contournant étonnamment leur flux de commande.

pour répondre à votre deuxième question: l'inconvénient de ce approche est clairement indiqué par l'autre réponse s: Vous êtes le public doit utiliser le même compilateur, linker, paramètres, bibliothèques que vous. Et s'ils pouvaient être assez beaucoup cela peut être inconvénient majeur pour votre bibliothèque. Vous devez choisir: la sécurité contre un public plus large

mais il y a une faille possible: utilisez shared_ptr<IBase> dans votre application, i.e.

{
    shared_ptr<IBase> p(CreateInterface(), DestroyFromLibrary);
    ...
    func();
    ...
}

donc pas de mise en œuvre spécifique l'objet est passé à travers la limite de la DLL. Néanmoins votre pointeur est en sécurité caché derrière le shared_ptr , qui appelle DestroyFromLibrary au bon moment, même si func() l 'jeter une exception ou non.

17
répondu phlipsy 2017-05-23 12:32:08

je déconseille d'utiliser shared_ptr dans l'interface. Même utiliser C++ du tout dans l'interface D'une DLL (par opposition aux routines "extern C" seulement) est problématique parce que la modification des noms vous empêchera d'utiliser la DLL avec un compilateur différent. L'utilisation de shared_ptr est particulièrement problématique car, comme vous l'avez déjà identifié, il n'y a aucune garantie que le client de la DLL utilisera la même implémentation de shared_ptr que l'appelant. (C'est parce que shared_ptr est une classe template et l'implémentation est contenue entièrement dans le fichier d'en-tête.)

pour répondre à vos questions spécifiques:

  1. Je ne suis pas tout à fait sûr de ce que vous demandez ici... Je suppose que votre DLL contiendra des implémentations de classes dérivées de IBase . Le code de leurs destructeurs (ainsi que le reste du code), dans les deux cas, être contenue dans la DLL. Cependant, si le client commence la destruction de l'objet (en appelant delete dans le premier cas ou en laissant la dernière instance du shared_ptr sortir de la portée dans le second cas), puis le destructeur sera appelé à partir du code client .

  2. nom-mangling empêchera généralement votre DLL d'être utilisé avec un compilateur différent de toute façon... mais la mise en œuvre de shared_ptr peut changer même dans une nouvelle version du même compilateur, et que peut vous causer des ennuis. J'hésiterais à utiliser la deuxième option.

6
répondu Martin B 2009-10-22 08:18:59
  1. en utilisant shared_ptr s'assurera que la fonction de libération de ressources sera appelée dans la DLL.
  2. regardez les réponses à cette question .

une façon de se sortir de ce problème est de créer une interface C pure et un emballage C++ mince autour de lui.

1
répondu sbi 2017-05-23 11:46:28

sur votre première question: je prends une supposition instruite et ne pas parler de l'expérience, mais il me semble que le second cas la désallocation de la mémoire sera appelé" dans le .EXE." Il y a deux choses qui se produisent quand vous appelez delete object; : premièrement, les destructeurs sont appelés et deuxièmement, la mémoire de l'objet est libérée. La première partie, destructor calling, fonctionnera certainement comme vous l'attendez, appelant les bons destructeurs dans votre dll. Cependant, puisque shared_ptr est un modèle de classe, son destructeur est générée dans votre .exe, et par conséquent il appellera l'opérateur delete () dans votre exe et pas celui dans le .DLL. Si les deux versions étaient liées par des versions différentes (ou même liées statiquement avec la même version) cela devrait conduire au comportement non défini redouté (c'est la partie dont je ne suis pas tout à fait sûr, mais il semble logique d'être ainsi). Il y a un moyen simple de vérifier si ce que j'ai dit est vrai - outrepasser l'opérateur global supprimer dans votre exe, mais pas votre dll, mettre un point de rupture et voir ce qui s'appelle dans le deuxième cas (je le ferais moi-même, mais j'ai ce beaucoup de temps pour se détendre, malheureusement).

notez que le même gotcha existe pour le premier cas (vous semblez vous en rendre compte, mais juste au cas). Si vous faites cela dans l'exe:

IBase *p = CreateInterface();
delete p;

ensuite, vous êtes dans le même opérateur de trap - calling nouveau dans la dll et opérateur d'appel supprimer dans l'exe. Vous aurez besoin d'un la fonction correspondante de DeleteInterface (IBase *p) dans votre dll ou une méthode de Release () dans IBase (qui ne doit pas être virtuelle, mais ne doit pas être en ligne) dans le seul but d'appeler la bonne fonction de deallocation de la mémoire.

1
répondu sbk 2009-10-22 09:51:47