Exportando classs contendo std :: objects (vetor, mapa, etc) de uma dll

Eu estou tentando exportar classs de uma DLL que contêm objects como std :: vectors e std :: strings – toda a class é declarada como exportação de dll através de:

class DLL_EXPORT FontManager { 

O problema é que para os membros dos tipos complexos eu recebo este aviso:

aviso C4251: ‘FontManager :: m__fonts’: class ‘std :: map ‘ precisa ter dll interface para ser usado por clientes da class ‘FontManager’ com [_Kty = std :: string, _Ty = tFontInfoRef ]

Eu sou capaz de remover alguns dos avisos, colocando a seguinte declaração de class de encaminhamento antes deles, mesmo que eu não estou mudando o tipo de variables ​​de membro em si:

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

Parece que a declaração de encaminhamento “injeta” o DLL_EXPORT para quando o membro é compilado, mas é seguro? Isso realmente muda alguma coisa quando o cliente compila esse header e usa o contêiner std do seu lado? Será que vai fazer todos os usos futuros de tal contêiner DLL_EXPORT (e possivelmente inline)? E isso realmente resolve o problema que o aviso tenta avisar?

Isso é algo que eu deveria estar preocupado ou seria melhor desativá-lo no escopo dessas construções? Os clientes e a dll sempre serão construídos usando o mesmo conjunto de bibliotecas e compiladores e eles são apenas classs de header …

Estou usando o Visual Studio 2003 com a biblioteca STD padrão.

—- Atualização —-

Eu gostaria de direcionar você mais, embora eu veja as respostas são gerais e aqui estamos falando sobre std containers e tipos (como std :: string) – talvez a questão seja realmente:

Podemos desativar o aviso para contêineres e tipos padrão disponíveis para o cliente e para a dll através dos mesmos headers de biblioteca e tratá-los da mesma forma que trataríamos um int ou qualquer outro tipo interno? (Parece funcionar corretamente do meu lado.) Se assim for, devem ser as condições sob as quais podemos fazer isso?

Ou talvez devesse usar tais contêineres ser proibido ou, pelo menos, ter o cuidado de não garantir que nenhum operador de atribuição, construtores de cópia, etc, entrem no cliente dll?

Em geral, eu gostaria de saber se você se sente projetando uma interface de dll tendo tais objects (e, por exemplo, usá-los para retornar coisas para o cliente como tipos de valor de retorno) é uma boa idéia ou não e por que – eu gostaria de ter uma interface de “alto nível” para essa funcionalidade … talvez a melhor solução seja o que Neil Butterworth sugeriu – criar uma biblioteca estática?

Quando você toca em um membro em sua class do cliente, você precisa fornecer uma interface DLL. Uma interface DLL significa que o compilador cria a function na própria DLL e a torna importável.

Como o compilador não sabe quais methods são usados ​​pelos clientes de uma class DLL_EXPORTED, ele deve impor que todos os methods sejam exportados para dll. Ele deve impor que todos os membros que podem ser acessados ​​pelos clientes também devem exportar suas funções. Isso acontece quando o compilador está avisando sobre methods não exportados e sobre o vinculador do cliente enviando erros.

Nem todos os membros devem ser marcados com a exportação de dll, por exemplo, membros privados não tocáveis ​​pelos clientes. Aqui você pode ignorar / desabilitar os avisos (cuidado com os dtor / ctors gerados pelo compilador).

Caso contrário, os membros devem exportar seus methods. Encaminhar declarando-os com DLL_EXPORT não exporta os methods dessas classs. Você tem que marcar as classs correspondentes em sua unidade de compilation como DLL_EXPORT.

O que se resume a … (para membros não exportáveis ​​de dll)

  1. Se você tiver membros que não são / não podem ser usados ​​pelos clientes, desative o aviso.

  2. Se você tiver membros que devem ser usados ​​por clientes, crie um wrapper de exportação de dll ou crie methods indiretos.

  3. Para reduzir a contagem de membros visíveis externamente, use abordagens como o idioma PIMPL .


 template class DLL_EXPORT std::allocator; 

Isso cria uma instanciação da especialização de modelo na unidade de compilation atual. Portanto, isso cria os methods de std :: allocator na dll e exporta os methods correspondentes. Isso não funciona para classs concretas, pois isso é apenas uma instanciação de classs de modelo.

Esse aviso está dizendo que os usuários da sua DLL não terão access às variables ​​de membro do contêiner através do limite da DLL. Explicitamente exportá-los os torna disponíveis, mas é uma boa ideia?

Em geral, eu evitaria exportar contêineres std da sua DLL. Se você pode absolutamente garantir que sua DLL será usada com a mesma versão de tempo de execução e compilador, você estará seguro. Você deve garantir que a memory alocada em sua DLL seja desalocada usando o mesmo gerenciador de memory. Fazer o contrário, na melhor das hipóteses, afirma em tempo de execução.

Portanto, não exponha contêineres diretamente nos limites de DLL. Se você precisar expor elementos de contêiner, faça isso por meio de methods de access. No caso que você forneceu, separe a interface da implementação e exponha a inteface no nível da DLL. Seu uso de contêineres std é um detalhe de implementação que o cliente da DLL não precisa acessar.

Como alternativa, faça o que o Neil sugere e crie uma biblioteca estática em vez de uma DLL. Você perde a capacidade de carregar a biblioteca em tempo de execução, e os consumidores de sua biblioteca devem revincular sempre que você alterar sua biblioteca. Se esses forem compromissos com os quais você pode conviver, uma biblioteca estática poderia, pelo menos, superar esse problema. Ainda vou argumentar que você está expondo detalhes da implementação desnecessariamente, mas isso pode fazer sentido para sua biblioteca particular.

Existem outros problemas.

Alguns contêineres STL são “seguros” para exportar (como vetor) e outros não (por exemplo, mapa).

Mapa, por exemplo, não é seguro porque (na distribuição do MS STL) contém um membro estático chamado _Nil, cujo valor é comparado na iteração para testar o final. Cada módulo compilado com STL tem um valor diferente para _Nil e, portanto, um mapa criado em um módulo não será iterável de outro módulo (ele nunca detecta o final e explode).

Isso se aplica mesmo se você vincular estaticamente a um lib, desde que você nunca pode garantir que o valor de _Nil será (é não inicializado).

Eu acredito que o STLPort não faz isso.

A melhor maneira que encontrei para lidar com esse cenário é:

crie sua biblioteca, nomeando-a com as versões do compilador e do stl incluídas no nome da biblioteca, exatamente como as bibliotecas de reforço.

exemplos:

– FontManager-msvc10-mt.dll para a versão dll, específico para o compilador MSVC10, com o stl padrão.

– FontManager-msvc10_stlport-mt.dll para a versão dll, específico para o compilador MSVC10, com a porta stl.

– FontManager-msvc9-mt.dll para a versão dll, específico para o compilador MSVC 2008, com o stl padrão

– libFontManager-msvc10-mt.lib para a versão estática do lib, específica para o compilador MSVC10, com o stl padrão.

Seguindo esse padrão, você evitará problemas relacionados a diferentes implementações do stl. lembre-se, a implementação do stl em vc2008 é diferente da implementação do stl no vc2010.

Veja o seu exemplo usando a biblioteca 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 

Uma alternativa que poucas pessoas parecem considerar é não usar uma DLL, mas vincular estaticamente a uma biblioteca .LIB estática. Se você fizer isso, todos os problemas de exportação / importação desaparecerão (embora você ainda tenha problemas de manuseio de nomes se usar compiladores diferentes). Naturalmente, você perde os resources da arquitetura DLL, como o carregamento de funções em tempo de execução, mas esse pode ser um pequeno preço a pagar em muitos casos.

Encontrei este artigo . Em suma Aaron tem a resposta ‘real’ acima; Não exponha contêineres padrão nos limites da biblioteca.

Embora esse segmento seja bem antigo, encontrei um problema recentemente, o que me fez pensar novamente em ter modelos nas minhas classs exportadas:

Eu escrevi uma class que tinha um membro particular do tipo std :: map. Tudo funcionou muito bem até que ele foi compilado no modo de liberação, mesmo quando usado em um sistema de compilation, o que garante que todas as configurações do compilador são as mesmas para todos os alvos. O mapa estava completamente oculto e nada foi exposto diretamente aos clientes.

Como resultado, o código estava apenas travando no modo de liberação. Eu pergunto, porque diferentes instâncias std :: map binárias foram criadas para implementação e código do cliente.

Eu acho que o padrão C ++ não está dizendo nada sobre como isso deve ser tratado para as classs exportadas como isso é praticamente específico do compilador. Então eu acho que a maior regra de portabilidade é apenas expor Interfaces e usar o idioma PIMPL, tanto quanto possível.

Obrigado por qualquer esclarecimento

Em tais casos, considere os usos do idioma pimpl. Esconda todos os tipos complexos atrás de um único vazio *. O compilador geralmente não percebe que seus membros são privados e todos os methods incluídos na DLL.

Exportando classs contendo std :: objects (vetor, mapa, etc) de uma dll

Consulte também o artigo KB 168958 da Microsoft Como exportar uma instanciação de uma class Standard Template Library (STL) e uma class que contenha um membro de dados que seja um object STL . Do artigo:

Para exportar uma class STL

  1. Na DLL e no arquivo .exe, vincule com a mesma versão DLL do tempo de execução C. Vincule ambos com Msvcrt.lib (versão compilation) ou vincular ambos com Msvcrtd.lib (compilation de debugging).
  2. Na DLL, forneça o especificador __declspec na declaração de instanciação de modelo para exportar a instanciação de class STL da DLL.
  3. No arquivo .exe, forneça os especificadores externos e __declspec na declaração de instanciação de modelo para importar a class da DLL. Isso resulta em um aviso C4231 “extensão não padrão usada: ‘extern’ antes da instanciação explícita do modelo.” Você pode ignorar esse aviso.

E:

Para exportar uma class que contém um membro de dados que é um object STL

  1. Na DLL e no arquivo .exe, vincule com a mesma versão DLL do tempo de execução C. Vincule ambos com Msvcrt.lib (versão compilation) ou vincular ambos com Msvcrtd.lib (compilation de debugging).
  2. Na DLL, forneça o especificador __declspec na declaração de instanciação de modelo para exportar a instanciação de class STL da DLL.

    Observação: você não pode ignorar a etapa 2. Você deve exportar a instanciação da class STL que você usa para criar o membro de dados.

  3. Na DLL, forneça o especificador __declspec na declaração da class para exportar a class da DLL.
  4. No arquivo .exe, forneça o especificador __declspec na declaração da class para importar a class da DLL. Se a class que você está exportando tiver uma ou mais classs base, você deverá exportar as classs base também.

    Se a class que você está exportando contém membros de dados que são do tipo de class, você também deve exportar as classs dos membros de dados.

Se você usar uma DLL, faça a boot de todos os objects no evento “DLL PROCESS ATTACH” e exporte um ponteiro para suas classs / objects.
Você pode fornecer funções específicas para criar e destruir objects e funções para obter o ponteiro dos objects criados, para que você possa encapsular essas chamadas em uma class de wrapper de access no arquivo de inclusão.

Nenhuma das soluções alternativas acima é aceitável com o MSVC devido aos membros de dados estáticos dentro de classs de modelo, como contêineres stl

cada módulo (dll / exe) recebe sua própria cópia de cada definição estática … uau! isso levará a coisas terríveis se você de alguma forma ‘exportar’ esses dados (como ‘apontados’ acima) … então não tente isso em casa

consulte http://support.microsoft.com/kb/172396/pt-br

A melhor abordagem a ser usada nesses cenários é usar o padrão de design PIMPL.