2009-10-22 18 views
20

Ho una classe astratta nella mia DLL.Uso di shared_ptr nelle interfacce dll

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

voglio ottenere IBase nel mio file exe che carica dll. primo modo è quello di creare seguente funzione

IBase * CreateInterface(); 

e di aggiungere la funzione virtuale Release() in IBase.

secondo modo è quello di creare un'altra funzione

boost::shared_ptr<IBase> CreateInterface(); 

e non è necessaria alcuna funzione Release().

Domande.

1) E 'vero che la deallocazione distruttore e la memoria è chiamata nella DLL (non in exe-file) in secondo caso?

2) Il il secondo caso funziona bene se exe-file e dll sono stati compilati con diversi compilatori (o impostazioni diverse).

risposta

5

Vorrei sconsigliare l'uso di shared_ptr nell'interfaccia. Anche l'uso di C++ sull'interfaccia di una DLL (al contrario delle routine "extern C") è problematico perché il manegging dei nomi impedisce di utilizzare la DLL con un compilatore diverso. L'utilizzo di shared_ptr è particolarmente problematico poiché, come già identificato, non è possibile garantire che il client della DLL utilizzi la stessa implementazione di shared_ptr come chiamante. (Questo perché shared_ptr è una classe template e l'implementazione è contenuto interamente nel file di intestazione.)

per rispondere alle vostre domande specifiche:

  1. Io non sono molto sicuro di quello che stai chiedendo qui. .. Sto assumendo che la tua DLL conterrà implementazioni di classi derivate da IBase. Il codice per i loro distruttori (così come il resto del codice) sarà, in entrambi i casi, contenuto nella DLL. Tuttavia, se il client avvia la distruzione dell'oggetto (chiamando delete nel primo caso o lasciando che l'ultima istanza dello shared_ptr esca dall'ambito nel secondo caso), il distruttore verrà chiamato dal codice client.

  2. Il manegging dei nomi di solito impedisce che la DLL venga utilizzata con un compilatore diverso ... ma l'implementazione di shared_ptr potrebbe cambiare anche in una nuova versione dello stesso compilatore e questo potrebbe metterti nei guai. Vorrei evitare di usare la seconda opzione.

+0

-1 perché * è possibile * associare l'eliminazione "destra" a shared_ptr e boost :: shared_ptr è già presente in tr1. Quindi è molto vicino a essere standard. +1 menzionare il nome di manomissione come un altro punto problematico = 0 ... e allora? – phlipsy

+1

Che è possibile specificare un deleter personalizzato per l'utilizzo da shared_ptr non è il problema. Il problema è che lo "standard-to-be" (TR1) non specifica come deve essere implementato un shared_ptr, cioè quale dovrebbe essere il suo layout di memoria e dove dovrebbe memorizzare il suo conteggio dei riferimenti. La DLL e il client possono essere compilati con due compilatori che sono entrambi completamente conformi agli standard, ma non sono d'accordo su cosa sia shared_ptr internamente. *Questo è il problema. Quindi non usare shared_ptr nelle interfacce DLL ... potrebbe tornare indietro e morderti. –

+0

@Marting: accetto, in entrambi i casi, il problema derivante da diverse implementazioni di shared_ptr non viene risolto dal deleter personalizzato. Ma poi la domanda di intestazione dovrebbe essere: è utile fornire un'interfaccia C++ per una DLL? – phlipsy

1
  1. Utilizzando shared_ptr farà in modo che la funzione di rilascio delle risorse sarà chiamata nella DLL.
  2. Dai un'occhiata alle risposte a this question.

Un modo per uscire da questo problema è creare un'interfaccia C pura e un involucro C++ completamente integrato in esso.

16

Una risposta alla tua prima domanda: Il distruttore virtuale nella tua DLL viene chiamato - le informazioni sulla sua posizione sono incorporate nel tuo oggetto (nel vtable). Nel caso della deallocazione della memoria dipende da quanto sono disciplinati gli utenti del tuo IBase. Se sanno che devono chiamare Release() e ritengono che l'eccezione possa bypassare il flusso di controllo in una direzione sorprendente, verrà utilizzato quello corretto.

Ma se CreateInterface() restituisce shared_ptr<IBase> è possibile associare la giusta funzione di deallocazione direttamente a questo puntatore intelligente. La vostra biblioteca può apparire come segue:

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 

Così ogni utente della DLL è facilmente prevenute contro perdite di risorse. Non devono mai preoccuparsi di chiamare Release() o prestare attenzione alle eccezioni ignorando sorprendentemente il loro flusso di controllo.

Per rispondere alla tua seconda domanda: Lo svantaggio di questo approccio è chiaramente indicato dagli altri answer s: Sei pubblico deve utilizzare lo stesso compilatore, linker, le impostazioni, le biblioteche come te. E se possono essere parecchio questo può essere un grosso svantaggio per la tua libreria. Devi scegliere: Sicurezza vs pubblico più vasto

ma c'è una possibile scappatoia: Uso shared_ptr<IBase> nell'applicazione, vale a dire

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

Così nessun oggetto specifica implementazione è passato attraverso il confine DLL. Tuttavia il tuo puntatore è nascosto in modo sicuro dietro lo shared_ptr, che sta chiamando DestroyFromLibrary al momento giusto anche se func() lancia un'eccezione o meno.

+1

Sembra esserci un errore di battitura nella risposta: "boost :: shared_ptr IBase * CreateInterface()". Rimuovi "IBase *" da quello. – shojtsy

+0

Hai assolutamente ragione. Grazie! – phlipsy

1

Sulla tua prima domanda: sto prendendo un'ipotesi e non parlo per esperienza, ma mi sembra che nel secondo caso la deallocazione della memoria sarà chiamata "nell'exe". Ci sono due cose che accadono quando chiami delete object;: prima vengono chiamati i distruttori e il secondo, la memoria per l'oggetto viene liberata. La prima parte, chiamata distruttore, funzionerà sicuramente come previsto, chiamando i destruttori giusti nella tua DLL. Tuttavia, poiché shared_ptr è un modello di classe, il suo distruttore viene generato nel file .exe e pertanto chiamerà operator delete() nel file exe e non nel file .dll. Se i due fossero collegati a versioni di runtime differenti (o anche collegate staticamente alla stessa versione runtime) ciò dovrebbe portare al comportamento indefinito temuto (questa è la parte di cui non sono completamente sicuro, ma sembra logico essere in quel modo) . C'è un modo semplice per verificare se quello che ho detto è vero - override the global operator delete nel tuo exe, ma non la tua DLL, inserire un breakpoint e vedere cosa viene chiamato nel secondo caso (lo farei anch'io, ma ho molto tempo per rallentare, sfortunatamente).

Si noti che lo stesso gotcha esiste per il primo caso (sembra che tu lo capisca, ma per ogni evenienza). Se si esegue questa operazione nel exe:

IBase *p = CreateInterface(); 
delete p; 

allora siete nella stessa trappola - chiamando operator new nella DLL e chiamare operatore di cancellare in exe. Avrai bisogno di una funzione DeleteInterface (IBase * p) corrispondente nel tuo dll o un metodo Release() in IBase (che non deve essere virtuale, ma non renderlo in linea) al solo scopo di chiamare la memoria giusta funzione di deallocazione.

Problemi correlati