Esportare le classi contenenti std :: objects (vettore, mappa, ecc.) Da una dll

Sto cercando di esportare le classi da una DLL che contiene oggetti come std :: vectors e std :: stringhe – l’intera class è dichiarata come esportazione di dll tramite:

class DLL_EXPORT FontManager { 

Il problema è che per i membri dei tipi complessi ottengo questo avvertimento:

warning C4251: ‘FontManager :: m__fonts’: la class ‘std :: map ‘ deve avere l’interfaccia dll per essere usata dai client della class ‘FontManager’ con [_Kty = std :: string, _Ty = tFontInfoRef ]

Sono in grado di rimuovere alcuni degli avvisi mettendo la seguente dichiarazione di class prima di loro anche se non sto cambiando il tipo delle variabili membro:

 template class DLL_EXPORT std::allocator; template class DLL_EXPORT std::vector<tCharGlyphProviderRef,std::allocator >; std::vector m_glyphProviders; 

Sembra che la dichiarazione diretta “inietta” il DLL_EXPORT per quando il membro è compilato ma è sicuro? Rende davvero qualcosa quando il client compila questa intestazione e usa il contenitore std dalla sua parte? Farà tutti gli usi futuri di un tale contenitore DLL_EXPORT (e possibilmente non in linea?)? E risolve davvero il problema che l’avvertimento cerca di mettere in guardia?

Questo avviso è qualcosa di cui dovrei preoccuparmi o sarebbe meglio disabilitarla nell’ambito di questi costrutti? I client e la DLL verranno sempre creati utilizzando lo stesso set di librerie e compilatori e quelli sono solo intestazione classi …

Sto usando Visual Studio 2003 con la libreria STD standard.

—- Aggiornare —-

Mi piacerebbe bersagliarti di più anche se vedo che le risposte sono generali e qui stiamo parlando di contenitori e tipi std (come std :: string) – forse la domanda è davvero:

Possiamo disabilitare l’avviso per i contenitori standard e i tipi disponibili sia per il client che per la dll attraverso le stesse intestazioni di libreria e trattarli proprio come tratteremmo un int o qualsiasi altro tipo built-in? (Sembra funzionare correttamente dalla mia parte.) Se così fosse, dovrebbero essere le condizioni alle quali possiamo fare questo?

O forse è meglio vietare l’utilizzo di tali contenitori o, se non altro, la massima cura nell’assicurarsi che nessun operatore di assegnazione, copia dei costruttori, ecc. Venga inserito nel client dll?

In generale mi piacerebbe sapere se pensi che progettare un’interfaccia dll con tali oggetti (e ad esempio usarli per restituire cose al client come tipi di valore di ritorno) sia una buona idea o meno e perché – Mi piacerebbe avere un’interfaccia “di alto livello” per questa funzionalità … forse la soluzione migliore è quella suggerita da Neil Butterworth: creare una libreria statica?

Quando si tocca un membro della class dal client, è necessario fornire un’interfaccia DLL. Un’interfaccia DLL significa che il compilatore crea la funzione nella DLL stessa e la rende imansible.

Poiché il compilatore non sa quali metodi vengono utilizzati dai client di una class DLL_EXPORTED, deve applicare che tutti i metodi sono esportati da dll. Deve far sì che tutti i membri a cui possono accedere i clienti debbano esportare anche le loro funzioni. Ciò accade quando il compilatore ti avvisa di metodi non esportati e del linker del client che invia errori.

Non tutti i membri devono essere contrassegnati con dll-export, ad esempio i membri privati ​​non consultabili dai clienti. Qui puoi ignorare / disabilitare gli avvertimenti (attenzione ai dtor / ctor generati dal compilatore).

Altrimenti i membri devono esportare i loro metodi. Inoltrare dichiarandoli con DLL_EXPORT non esporta i metodi di queste classi. Devi contrassegnare le classi corrispondenti nella loro unità di compilazione come DLL_EXPORT.

Cosa si riduce a … (per i membri non esportabili)

  1. Se si dispone di membri che non sono / non possono essere utilizzati dai client, distriggersre l’avviso.

  2. Se si dispone di membri che devono essere utilizzati dai client, creare un wrapper dll-export o creare metodi di riferimento indiretto.

  3. Per ridurre il numero di membri visibili esternamente, utilizzare approcci come l’ idioma PIMPL .


 template class DLL_EXPORT std::allocator; 

Ciò crea un’istanza della specializzazione del modello nell’unità di compilazione corrente. Quindi questo crea i metodi di std :: allocator nella DLL ed esporta i metodi corrispondenti. Questo non funziona per le classi concrete in quanto questa è solo un’istanza delle classi template.

Quell’avvertimento ti sta dicendo che gli utenti della tua DLL non avranno accesso alle variabili del tuo membro del contenitore attraverso il limite della DLL. Esportarli esplicitamente li rende disponibili, ma è una buona idea?

In generale, eviterei di esportare i contenitori std dalla tua DLL. Se puoi assolutamente garantire che la tua DLL verrà utilizzata con la stessa versione del runtime e del compilatore, sarai al sicuro. È necessario assicurarsi che la memoria allocata nella DLL sia deallocata utilizzando lo stesso gestore di memoria. Fare altrimenti, nel migliore dei casi, valuterà in fase di runtime.

Quindi, non esporre i contenitori direttamente attraverso i confini della DLL. Se è necessario esporre gli elementi del contenitore, farlo tramite i metodi accessor. Nel caso in cui è stato fornito, separare l’interfaccia dall’implementazione ed esporre l’intefaccia a livello di DLL. L’utilizzo di contenitori std è un dettaglio di implementazione a cui il client della DLL non deve accedere.

In alternativa, esegui ciò che Neil suggerisce e crea una libreria statica anziché una DLL. Si perde la possibilità di caricare la libreria in fase di esecuzione e gli utenti della libreria devono ricollegarsi ogni volta che si modifica la libreria. Se questi sono compromessi con cui puoi convivere, una libreria statica dovrebbe almeno farti superare questo problema. Continuerò a sostenere che stai esponendo i dettagli di implementazione inutilmente, ma potrebbe avere senso per la tua libreria specifica.

Ci sono altri problemi

Alcuni contenitori STL sono “sicuri” da esportare (come il vettore), mentre altri non lo sono (es. Mappa).

La mappa per esempio non è sicura perché (nella distribuzione di MS STL) contiene un membro statico chiamato _Nil, il cui valore viene confrontato in iterazione per verificare la fine. Ogni modulo compilato con STL ha un valore diverso per _Nil, quindi una mappa creata in un modulo non sarà iterabile da un altro modulo (non rileva mai la fine e scoppia).

Ciò si applica anche se si collega staticamente a una lib, poiché non si può mai garantire quale sarà il valore di _Nil (non inizializzato).

Credo che STLPort non lo faccia.

Il modo migliore che ho trovato per gestire questo scenario è:

crea la tua libreria, nominandola con le versioni del compilatore e di stl incluse nel nome della libreria, esattamente come fanno le librerie di boost.

esempi:

– FontManager-msvc10-mt.dll per la versione dll, specifica per il compilatore MSVC10, con il valore predefinito stl.

– FontManager-msvc10_stlport-mt.dll per la versione dll, specifica per il compilatore MSVC10, con la porta stl.

– FontManager-msvc9-mt.dll per la versione dll, specifica per il compilatore MSVC 2008, con il valore predefinito stl

– libFontManager-msvc10-mt.lib per la versione statica della lib, specifica per il compilatore MSVC10, con il valore predefinito stl.

seguendo questo schema, si eviteranno problemi relativi a diverse implementazioni di stl. ricorda che l’implementazione di stl in vc2008 differisce dall’implementazione di stl nel vc2010.

Guarda il tuo esempio usando la libreria boost :: config:

 #include  #ifdef BOOST_MSVC # pragma warning( push ) # pragma warning( disable: 4251 ) #endif class DLL_EXPORT FontManager { public: std::map int2string_map; } #ifdef BOOST_MSVC # pragma warning( pop ) #endif 

Un’alternativa che poche persone sembrano considerare non è quella di utilizzare una DLL, ma di colbind staticamente una libreria .LIB statica. Se lo fai, tutti i problemi di esportazione / importazione vanno via (anche se avrai ancora problemi di manomissione dei nomi se usi compilatori diversi). Ovviamente perdi le funzionalità dell’architettura DLL, come il caricamento delle funzioni in fase di esecuzione, ma in molti casi questo può essere un piccolo prezzo da pagare.

Ho trovato questo articolo In breve, Aaron ha la “vera” risposta sopra; Non esporre contenitori standard oltre i confini della biblioteca.

Anche se questo thread è piuttosto vecchio, ho riscontrato un problema di recente, che mi ha fatto ripensare all’idea di avere modelli nelle mie classi esportate:

Ho scritto una class che aveva un membro privato di tipo std :: map. Tutto ha funzionato abbastanza bene fino a quando non è stato compilato in modalità di rilascio, anche se utilizzato in un sistema di compilazione, che garantisce che tutte le impostazioni del compilatore siano uguali per tutti i target. La mappa era completamente nascosta e nulla veniva direttamente esposto ai clienti.

Di conseguenza, il codice si stava bloccando in modalità di rilascio. I gues, perché sono state create diverse istanze binarie std :: map per l’implementazione e il codice client.

Immagino che lo standard C ++ non stia dicendo tutto su come questo debba essere gestito per le classi esportate in quanto questo è più o meno specifico del compilatore. Quindi immagino che la più grande regola della portabilità sia quella di esporre semplicemente le interfacce e utilizzare l’idioma PIMPL il più ansible.

Grazie per ogni chiarimento

In questi casi, considera gli usi dell’idioma di Pimpl. Nascondere tutti i tipi complessi dietro un singolo vuoto *. Generalmente il compilatore non riesce a notare che i membri sono privati ​​e tutti i metodi inclusi nella DLL.

Esportare le classi contenenti std :: objects (vettore, mappa, ecc.) Da una dll

Vedere anche l’articolo KB 168958 di Microsoft Come esportare un’istanza di una class Standard Template Library (STL) e una class che contiene un membro dati che è un object STL . Dall’articolo:

Per esportare una class STL

  1. Sia nella DLL che nel file .exe, il collegamento con la stessa versione DLL del tempo di esecuzione C. Collega entrambi con Msvcrt.lib (release build) o collega entrambi con Msvcrtd.lib (build di debug).
  2. Nella DLL, fornire l’identificatore __declspec nella dichiarazione di istanza del modello per esportare l’istanza della class STL dalla DLL.
  3. Nel file .exe, fornire gli specificatori extern e __declspec nella dichiarazione di istanza del modello per importare la class dalla DLL. Ciò si traduce in un avviso C4231 “estensione non standard utilizzata: ‘extern’ prima dell’istanza esplicativa del modello.” Puoi ignorare questo avviso.

E:

Per esportare una class contenente un membro dati che è un object STL

  1. Sia nella DLL che nel file .exe, il collegamento con la stessa versione DLL del tempo di esecuzione C. Collega entrambi con Msvcrt.lib (release build) o collega entrambi con Msvcrtd.lib (build di debug).
  2. Nella DLL, fornire l’identificatore __declspec nella dichiarazione di istanza del modello per esportare l’istanza della class STL dalla DLL.

    NOTA: non è ansible saltare il passaggio 2. È necessario esportare l’istanza della class STL che si utilizza per creare il membro dati.

  3. Nella DLL, fornire l’identificatore __declspec nella dichiarazione della class per esportare la class dalla DLL.
  4. Nel file .exe, fornire l’identificatore __declspec nella dichiarazione della class per importare la class dalla DLL. Se la class che si sta esportando ha una o più classi base, è necessario esportare anche le classi base.

    Se la class che si sta esportando contiene membri dati di tipo class, è necessario esportare anche le classi dei membri dati.

Se si utilizza una DLL, rendere l’inizializzazione di tutti gli oggetti all’evento “DLL PROCESS ATTACH” ed esportare un puntatore alle sue classi / oggetti.
È ansible fornire funzioni specifiche per creare e distruggere oggetti e funzioni per ottenere il puntatore degli oggetti creati, in modo da poter incapsulare queste chiamate in una class di accesso wrapper al file include.

nessuno dei rimedi sopra descritti è accettabile con MSVC a causa dei membri di dati statici all’interno di classi di template come i contenitori di stl

ogni modulo (dll / exe) ottiene la propria copia di ogni definizione statica … wow! questo porterà a cose terribili se in qualche modo “esportate” questi dati (come “indicato” sopra) … quindi non provatelo a casa

vedere http://support.microsoft.com/kb/172396/en-us

L’approccio migliore da utilizzare in tali scenari consiste nell’utilizzare il modello di progettazione PIMPL.