Perché questo programma si arresta in modo anomalo: passaggio di std :: string tra DLL

Ho qualche problema a capire perché i seguenti arresti anomali (MSVC9):

//// the following compiles to A.dll with release runtime linked dynamically //Ah class A { __declspec(dllexport) std::string getString(); }; //A.cpp #include "Ah" std::string A::getString() { return "I am a string."; } //// the following compiles to main.exe with debug runtime linked dynamically #include "Ah" int main() { A a; std::string s = a.getString(); return 0; } // crash on exit 

Ovviamente (?) Ciò è dovuto ai diversi modelli di memoria per l’eseguibile e la DLL. Potrebbe essere che la stringa A::getString() restituisce viene allocata in A.dll e liberata in main.exe?

Se è così, perché – e quale sarebbe un modo sicuro per passare stringhe tra DLL (o eseguibili, per quella materia)? Senza usare wrapper come shared_ptr con un deleter personalizzato.

Questo non è in realtà causato da diverse implementazioni dell’heap: l’implementazione MSVC std :: string non utilizza la memoria allocata dynamicmente per stringhe così piccole (utilizza l’ottimizzazione della stringa piccola). I CRT devono combaciare, ma non è questo che hai morso questa volta.

Quello che sta accadendo è che stai invocando un comportamento indefinito violando la regola One Definition .

Le versioni di rilascio e di debug avranno flag di preprocessore diversi e scoprirai che std::string ha una definizione diversa in ciascun caso. Chiedi al tuo compilatore che sizeof(std::string) è – MSVC10 mi dice che è 32 in una build di debug e 28 in una build di rilascio (questo non è padding – 28 e 32 sono entrambi i limiti di 4 byte).

Quindi cosa sta succedendo? La variabile s viene inizializzata usando la versione di debug del costruttore di copie per copiare una versione di rilascio di std::string . Le compensazioni delle variabili membro sono diverse tra le versioni, quindi copiate garbage. L’implementazione MSVC memorizza in modo efficace i puntatori di inizio e fine: ne è stata copiata l’immondizia; poiché non sono più nulli, il distruttore tenta di liberarli e si ottiene una violazione di accesso.

Anche se le implementazioni dell’heap fossero le stesse, si arresterebbero in modo anomalo, dato che si stanno liberando dei puntatori obsoleti sulla memoria che non è mai stata allocata in primo luogo.


In sintesi: le versioni CRT devono corrispondere, ma anche le definizioni, incluse le definizioni nella libreria standard .

Potrebbe essere che la stringa A :: getString () restituisce viene allocata in A.dll e liberata in main.exe?

Sì.

Se è così, perché – e quale sarebbe un modo sicuro per passare stringhe tra DLL (o eseguibili, per quella materia)? Senza usare wrapper come shared_ptr con un deleter personalizzato.

Usare un shared_ptr suona come una cosa sensata per me. Ricorda, come regola generale, le allocazioni e le deallocations dovrebbero essere fatte dallo stesso modulo per evitare problemi come questi.

Esportare oggetti STL su dll è, nel migliore dei casi, un pony difficile. Suggerisco di controllare prima questo articolo MSDN KB e questo post.

Devi colbind alla stessa lib runtime (quella DLL), debug o release, per ogni DLL nella tua app in cui la memoria è allocata in una e liberata in un’altra. (La ragione per usare la lib di runtime collegata dynamicmente è che quindi ci sarà un heap per l’intero processo, in contrapposizione a uno per dll / exe che si collega a quello statico.)

Ciò include la restituzione di std :: string e stl-container in base al valore, poiché questo è ciò che fai.

I motivi sono due (sezione aggiornata) :

  • le classi hanno diversi layout / dimensioni, quindi il codice compilato in modo diverso presuppone che i dati si trovino in luoghi diversi. Chi l’ha creato per primo ha ragione, ma l’altro causerà un incidente prima o poi.
  • le implementazioni dell’heap di msvc sono diverse in ogni runtime-lib, il che significa che se si tenta di liberare un puntatore nell’heap che non lo ha allocato, andrà molto forte. (Questo succede se i layout sono simili, cioè dove sopravvivi al primo caso.)

Quindi, prendi le tue librerie di runtime diritte, o smetti di liberare / allocare in dll differenti (cioè smetti di passare cose per valore).

Oltre a quanto detto sopra, assicurati che il set di strumenti della piattaforma (in Proprietà-> Generale) sia identico in entrambi i progetti. In caso contrario, il contenuto della stringa sul lato in arrivo potrebbe essere fasullo.

Questo è successo a me quando un progetto di applicazione della console con la versione del toolset v100 ha consumato una libreria impostata su v90.

Ciò potrebbe essere dovuto al fatto che la DLL e l’EXE sono compilati con impostazioni CRT diverse. Quindi quando si passa una stringa, si verifica un conflitto di risorse. Controlla le impostazioni del tuo progetto sia per la DLL che per l’eseguibile.