visual tutorial studio microsoft how files crear archivos c++ visual-studio dll

c++ - tutorial - Exportación de clases que contienen std:: objetos(vector, mapa, etc.) desde un dll



microsoft dll (12)

Estoy intentando exportar clases desde un archivo DLL que contiene objetos como std :: vectores y std :: cadenas - toda la clase se declara como exportación dll a través de:

class DLL_EXPORT FontManager {

El problema es que para los miembros de los tipos complejos recibo esta advertencia:

Advertencia C4251: ''FontManager :: m__fonts'': class ''std :: map <_Kty, _Ty>'' necesita tener dll-interface para ser utilizado por los clientes de la clase ''FontManager'' con [_Kty = std :: string, _Ty = tFontInfoRef ]

Puedo eliminar algunas de las advertencias al presentarles la siguiente declaración de clase directa, aunque no estoy cambiando el tipo de las variables miembro por sí mismas:

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

Parece que la declaración directa "inyecta" el DLL_EXPORT para cuando se compila el miembro, pero ¿es seguro? ¿Realmente cambia algo cuando el cliente compila este encabezado y usa el contenedor estándar de su lado? ¿Hará todos los usos futuros de dicho contenedor DLL_EXPORT (y posiblemente no en línea?)? ¿Realmente resuelve el problema que la advertencia intenta advertir?

¿Es esta advertencia algo de lo que debería preocuparme o sería mejor desactivarla en el alcance de estos constructos? Los clientes y el dll siempre se compilarán usando el mismo conjunto de bibliotecas y compiladores, y esas son solo clases de encabezado ...

Estoy usando Visual Studio 2003 con la biblioteca STD estándar.

---- Actualización ----

Sin embargo, me gustaría enfocarte más en ti ya que veo que las respuestas son generales y aquí estamos hablando de contenedores y tipos estándar (como std :: string). Tal vez la pregunta realmente sea:

¿Podemos deshabilitar la advertencia para contenedores y tipos estándar disponibles tanto para el cliente como para el dll a través de los mismos encabezados de biblioteca y tratarlos de la misma manera que trataríamos un int o cualquier otro tipo de función incorporada? (Parece que funciona correctamente de mi parte). De ser así, ¿cuáles deberían ser las condiciones bajo las cuales podemos hacer esto?

¿O debería tal vez prohibirse el uso de tales contenedores o al menos ser extremadamente cuidadosos para asegurarse de que ningún operador de asignación, copiador, etc. ingrese en el cliente dll?

En general, me gustaría saber si sientes que diseñar una interfaz dll que tenga tales objetos (y, por ejemplo, usarlos para devolver cosas al cliente como tipos de valor devuelto) es una buena idea o no, y por qué - Me gustaría tener una interfaz de "alto nivel" para esta funcionalidad ... tal vez la mejor solución es lo que sugirió Neil Butterworth: ¿crear una biblioteca estática?


Exportación de clases que contienen std :: objetos (vector, mapa, etc.) desde un dll

Consulte también el artículo KB 168958 de Microsoft Cómo exportar una instanciación de una clase Biblioteca de plantillas estándar (STL) y una clase que contiene un miembro de datos que es un objeto STL . Del artículo:

Para exportar una clase STL

  1. Tanto en el archivo DLL como en el archivo .exe, enlace con la misma versión DLL del tiempo de ejecución de C. O bien enlace con Msvcrt.lib (versión de lanzamiento) o enlace ambos con Msvcrtd.lib (compilación de depuración).
  2. En la DLL, proporcione el especificador __declspec en la declaración de instanciación de la plantilla para exportar la instanciación de la clase STL desde la DLL.
  3. En el archivo .exe, proporcione los especificadores extern y __declspec en la declaración de instanciación de la plantilla para importar la clase desde la DLL. Esto da como resultado una advertencia C4231 "extensión no estándar utilizada: ''extern'' antes de instanciación explícita de la plantilla." Puedes ignorar esta advertencia.

Y:

Para exportar una clase que contiene un miembro de datos que es un objeto STL

  1. Tanto en el archivo DLL como en el archivo .exe, enlace con la misma versión DLL del tiempo de ejecución de C. O bien enlace con Msvcrt.lib (versión de lanzamiento) o enlace ambos con Msvcrtd.lib (compilación de depuración).
  2. En la DLL, proporcione el especificador __declspec en la declaración de instanciación de la plantilla para exportar la instanciación de la clase STL desde la DLL.

    NOTA: No puede omitir el paso 2. Debe exportar la instanciación de la clase STL que usa para crear el miembro de datos.
  3. En la DLL, proporcione el especificador __declspec en la declaración de la clase para exportar la clase desde la DLL.
  4. En el archivo .exe, proporcione el especificador __declspec en la declaración de la clase para importar la clase desde la DLL. Si la clase que está exportando tiene una o más clases base, también debe exportar las clases base.

    Si la clase que está exportando contiene miembros de datos que son del tipo de clase, también debe exportar las clases de los miembros de datos.

Aunque este hilo es bastante antiguo, recientemente encontré un problema, lo que me hizo pensar nuevamente en tener plantillas en mis clases exportadas:

Escribí una clase que tenía un miembro privado de tipo std :: map. Todo funcionó bastante bien hasta que se compiló en modo de lanzamiento, incluso cuando se usa en un sistema de compilación, lo que garantiza que todas las configuraciones del compilador sean las mismas para todos los objetivos. El mapa estaba completamente oculto y no había nada directamente expuesto a los clientes.

Como resultado, el código simplemente se bloqueaba en el modo de lanzamiento. Supongo, porque se crearon diferentes instancias binarias std :: map para implementación y código de cliente.

Supongo que el estándar de C ++ no dice nada sobre cómo se manejará esto para las clases exportadas ya que esto es bastante específico del compilador. Así que supongo que la regla de portabilidad más grande es simplemente exponer Interfaces y usar el modismo PIMPL tanto como sea posible.

Gracias por cualquier aclaración


Cuando toca un miembro de su clase desde el cliente, debe proporcionar una interfaz DLL. Una interfaz DLL significa que el compilador crea la función en la DLL y la hace importable.

Como el compilador no sabe qué métodos utilizan los clientes de una clase DLL_EXPORTED, debe exigir que todos los métodos se exporten dll. Debe hacer cumplir que todos los miembros a los que pueden acceder los clientes también deben exportar sus funciones. Esto ocurre cuando el compilador le advierte sobre los métodos no exportados y el enlazador del cliente que envía los errores.

No todos los miembros deben estar marcados con dll-export, por ejemplo, miembros privados que los clientes no pueden tocar. Aquí puede ignorar / deshabilitar las advertencias (tenga cuidado con los dtor / ctors generados por el compilador).

De lo contrario, los miembros deben exportar sus métodos. Reenviarlos con DLL_EXPORT no exporta los métodos de estas clases. Debe marcar las clases correspondientes en su unidad de compilación como DLL_EXPORT.

Lo que se reduce a ... (para miembros que no son exportables a dll)

  1. Si tiene miembros que no son / no pueden ser utilizados por los clientes, desactive la advertencia.

  2. Si tiene miembros que los clientes deben usar, cree un contenedor dll-export o cree métodos indirectos.

  3. Para reducir el recuento de miembros visible externamente, utilice enfoques como el modismo PIMPL .

template class DLL_EXPORT std::allocator<tCharGlyphProviderRef>;

Esto crea una instancia de la especialización de plantilla en la unidad de compilación actual. Entonces esto crea los métodos de std :: allocator en el dll y exporta los métodos correspondientes. Esto no funciona para clases concretas ya que esto es solo una instancia de clases de plantilla.


El mejor enfoque para usar en tales escenarios es usar el patrón de diseño PIMPL.


En tales casos, considere los usos del idioma pimpl. Ocultar todos los tipos complejos detrás de un único vacío *. El compilador normalmente no se da cuenta de que sus miembros son privados y todos los métodos están incluidos en el archivo DLL.


Encontrado este articulo En resumen, Aaron tiene la respuesta ''real'' arriba; No exponga contenedores estándar a través de los límites de la biblioteca.


Esa advertencia le indica que los usuarios de su archivo DLL no tendrán acceso a las variables de miembro del contenedor a través del límite de la DLL. Exportarlos explícitamente los pone a disposición, pero ¿es una buena idea?

En general, evitaría exportar contenedores estándar desde su DLL. Si puede garantizar que su DLL se usará con el mismo tiempo de ejecución y la misma versión del compilador, estaría seguro. Debe asegurarse de que la memoria asignada en su DLL se elimine utilizando el mismo administrador de memoria. De lo contrario, lo hará, en el mejor de los casos, afirmar en tiempo de ejecución.

Por lo tanto, no exponga los contenedores directamente a través de los límites de la DLL. Si necesita exponer los elementos del contenedor, hágalo a través de los métodos de acceso. En el caso que proporcionó, separe la interfaz de la implementación y exponga la interfaz en el nivel de la DLL. Su uso de contenedores estándar es un detalle de implementación al que el cliente de su DLL no debería tener acceso.

Alternativamente, haga lo que sugiere Neil y cree una biblioteca estática en lugar de una DLL. Se pierde la capacidad de cargar la biblioteca en tiempo de ejecución, y los consumidores de su biblioteca deben volver a vincular cada vez que cambie su biblioteca. Si estos son compromisos con los que puedes vivir, una biblioteca estática al menos te ayudará a superar este problema. Seguiré argumentando que está exponiendo los detalles de implementación innecesariamente, pero podría tener sentido para su biblioteca en particular.


Hay otros problemas

Algunos contenedores STL son "seguros" para exportar (como el vector) y otros no (por ejemplo, un mapa).

Por ejemplo, el mapa no es seguro porque (de todos modos, en la distribución MS STL) contiene un miembro estático llamado _Nil, cuyo valor se compara en iteración para probar el final. Cada módulo compilado con STL tiene un valor diferente para _Nil, por lo que un mapa creado en un módulo no será iterable desde otro módulo (nunca detecta el extremo y explota).

Esto se aplicaría incluso si enlazas estáticamente a una lib, ya que nunca puedes garantizar cuál será el valor de _Nil (no está inicializado).

Creo que STLPort no hace esto.


La mejor forma que encontré para manejar este escenario es:

cree su biblioteca, nómbrela con el compilador y las versiones stl incluidas en el nombre de la biblioteca, exactamente como lo hacen las bibliotecas de impulso.

ejemplos:

- FontManager-msvc10-mt.dll para la versión dll, específico para el compilador MSVC10, con el stl predeterminado.

- FontManager-msvc10_stlport-mt.dll para la versión dll, específica para el compilador MSVC10, con el puerto stl.

- FontManager-msvc9-mt.dll para la versión dll, específico para el compilador MSVC 2008, con el stl predeterminado

- libFontManager-msvc10-mt.lib para la versión de lib estática, específica para el compilador MSVC10, con el stl predeterminado.

siguiendo este patrón, evitará problemas relacionados con diferentes implementaciones stl. recuerde, la implementación stl en vc2008 difiere de la implementación stl en vc2010.

Vea su ejemplo usando la biblioteca boost :: config:

#include <boost/config.hpp> #ifdef BOOST_MSVC # pragma warning( push ) # pragma warning( disable: 4251 ) #endif class DLL_EXPORT FontManager { public: std::map<int, std::string> int2string_map; } #ifdef BOOST_MSVC # pragma warning( pop ) #endif


Si utiliza una DLL, haga la inicialización de todos los objetos en el evento "DLL PROCESS Attach" y exporte un puntero a sus clases / objetos.
Puede proporcionar funciones específicas para crear y destruir objetos y funciones para obtener el puntero de los objetos creados, de modo que pueda encapsular estas llamadas en una clase contenedora de acceso en el archivo de inclusión.


Una alternativa que pocas personas parecen considerar es no utilizar una DLL en absoluto, sino vincular estáticamente contra una biblioteca .LIB estática. Si lo haces, todos los problemas de exportación / importación desaparecerán (aunque seguirás teniendo problemas para cambiar nombres si utilizas compiladores diferentes). Por supuesto, usted pierde las características de la arquitectura DLL, como la carga de funciones en tiempo de ejecución, pero esto puede ser un pequeño precio a pagar en muchos casos.


ninguna de las soluciones anteriores son aceptables con MSVC debido a los miembros de datos estáticos dentro de las clases de plantillas, como los contenedores stl

cada módulo (dll / exe) obtiene su propia copia de cada definición estática ... ¡guau! esto conducirá a cosas terribles si de alguna manera ''exporta'' tales datos (como ''señaló'' arriba) ... así que no intente esto en casa

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