Por que este programa falha: passagem de std :: string entre DLLs

Eu tenho alguns problemas para descobrir por que as seguintes falhas (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 

Obviamente (?) Isso é devido aos diferentes modelos de memory para o executável e DLL. Será que a string A::getString() retorna está sendo alocada em A.dll e liberada em main.exe?

Em caso afirmativo, por que – e qual seria uma maneira segura de passar seqüências de caracteres entre DLLs (ou executáveis, para esse assunto)? Sem usar wrappers como shared_ptr com um deleter personalizado.

Isso não está sendo causado por diferentes implementações de heap – a implementação std :: string do MSVC não usa memory alocada dinamicamente para cadeias pequenas (usa a otimização de cadeias pequenas). Os CRTs precisam combinar, mas isso não é o que você mordeu neste momento.

O que está acontecendo é que você está invocando um comportamento indefinido ao violar a regra de uma definição .

As compilações de release e debugging terão diferentes sinalizadores de pré-processador definidos e você verá que std::string tem uma definição diferente em cada caso. Pergunte ao seu compilador que sizeof(std::string) é – MSVC10 me diz que é 32 em uma compilation de debugging e 28 em uma compilation de release (isso não é preenchimento – 28 e 32 são limites de 4 bytes).

Então oque está acontecendo? A variável s é inicializada usando a versão de debugging do construtor de cópia para copiar uma versão de release de std::string . Os deslocamentos das variables ​​de membro são diferentes entre as versões, então você copia lixo. A implementação do MSVC armazena efetivamente os pointers de início e fim – você copiou o lixo para eles; porque eles não são mais nulos, o destruidor tenta liberá-los e você recebe uma violação de access.

Mesmo que as implementações do heap fossem as mesmas, elas iriam falhar, já que você está liberando pointers de lixo para a memory que nunca foi alocada em primeiro lugar.


Em resumo: as versões CRT precisam corresponder, assim como as definições – incluindo as definições na biblioteca padrão .

Será que a string A :: getString () retorna está sendo alocada em A.dll e liberada em main.exe?

Sim.

Em caso afirmativo, por que – e qual seria uma maneira segura de passar seqüências de caracteres entre DLLs (ou executáveis, para esse assunto)? Sem usar wrappers como shared_ptr com um deleter personalizado.

Usando um shared_ptr soa como uma coisa sensata para mim. Lembre-se, como regra geral, alocações e desalocações devem ser feitas pelo mesmo módulo para evitar falhas como essas.

Exportar objects STL através de dlls é, na melhor das hipóteses, um pônei complicado. Eu sugiro que você verifique este artigo KB MSDN primeiro e este post.

Você precisa se vincular à mesma biblioteca de tempo de execução (a DLL), seja para debugging ou release, para cada DLL em seu aplicativo em que a memory é alocada em uma e liberada em outra. (A razão para usar o lib de tempo de execução dinamicamente vinculado é que, em seguida, haverá um heap para todo o processo, em vez de um por dll / exe vinculado ao estático).

Isso inclui retornar std :: string e stl-containers por valor, pois é isso que você faz.

As razões são duplas (seção atualizada) :

  • as classs têm layouts / tamanhos diferentes, portanto, códigos compilados de maneira diferente pressupõem que os dados estão em lugares diferentes. Quem o criou primeiro acerta, mas o outro causará uma falha mais cedo ou mais tarde.
  • as implementações de heap msvc são diferentes em cada biblioteca de tempo de execução, o que significa que, se você tentar liberar um ponteiro no heap que não o alocou, ele ficará maluco. (Isso acontece se os layouts forem semelhantes, ou seja, onde você sobrevive ao primeiro caso).

Então, pegue suas libs de tempo de execução diretamente, ou pare de liberar / alocar em dlls diferentes (ou seja, pare de passar coisas por valor).

Além do que foi dito acima, certifique-se de que o Toolset da plataforma (em Properties-> General) seja idêntico em ambos os projetos. Caso contrário, o conteúdo da string no lado que chega pode ser falso.

Isso aconteceu comigo quando um projeto de aplicativo de console com a versão do conjunto de ferramentas v100 consumiu uma biblioteca que foi definida como v90.

Isso pode ser porque a DLL e o EXE são compilados com diferentes configurações de CRT. Então, quando você passa uma string, algum conflito de resources acontece. Verifique as configurações do seu projeto para a DLL e o executável.