[C++] ¿Cómo paso con seguridad objetos, especialmente objetos STL, hacia y desde una DLL?


Answers

@computerfreaker ha escrito una gran explicación de por qué la falta de ABI evita pasar objetos de C ++ a través de límites de DLL en el caso general, incluso cuando las definiciones de tipo están bajo el control del usuario y la misma secuencia de token se usa en ambos programas. (Hay dos casos que funcionan: clases de diseño estándar e interfaces puras)

Para los tipos de objetos definidos en el Estándar C ++ (incluidos los adaptados de la Biblioteca estándar de plantillas), la situación es mucho, mucho peor. Los tokens que definen estos tipos NO son los mismos en múltiples compiladores, ya que el estándar de C ++ no proporciona una definición de tipo completa, solo requisitos mínimos. Además, la búsqueda de nombre de los identificadores que aparecen en estas definiciones de tipo no resuelve el mismo. Incluso en sistemas donde hay un ABI de C ++, intentar compartir dichos tipos a través de los límites del módulo da como resultado un comportamiento indefinido masivo debido a las infracciones de la regla de una sola definición.

Esto es algo a lo que los programadores de Linux no estaban acostumbrados, porque la libstdc ++ de g ++ era un estándar de facto y virtualmente todos los programas lo usaban, satisfaciendo así la ODR. la librería libc ++ de clang rompió esa suposición, y luego apareció C ++ 11 con cambios obligatorios para casi todos los tipos de bibliotecas estándar.

Simplemente no comparta tipos de biblioteca estándar entre módulos. Es un comportamiento indefinido.

Question

¿Cómo paso objetos de clase, especialmente objetos STL, hacia y desde una DLL de C ++? Mi aplicación tiene que interactuar con complementos de terceros en forma de DLL, y no puedo controlar con qué compilador se crean estos complementos. Soy consciente de que no hay un ABI garantizado para objetos STL, y me preocupa causar inestabilidad en mi aplicación.




Algunas de las respuestas aquí hacen que pasar clases de C ++ suene realmente aterrador, pero me gustaría compartir un punto de vista alternativo. El método virtual puro de C ++ mencionado en algunas de las otras respuestas en realidad resulta ser más limpio de lo que piensas. Creé un sistema completo de complementos alrededor del concepto y ha funcionado muy bien durante años. Tengo una clase "PluginManager" que carga dinámicamente las dlls desde un directorio específico usando LoadLib () y GetProcAddress () (y los equivalentes de Linux para que el ejecutable lo haga en plataforma cruzada).

Créalo o no, este método es tolerante, incluso si usted hace algunas cosas extravagantes, como agregar una nueva función al final de su interfaz virtual pura e intentar cargar dlls compilados contra la interfaz sin esa nueva función: se cargarán bien. Por supuesto ... tendrás que verificar un número de versión para asegurarte de que tu ejecutable solo llame a la nueva función para dlls más nuevos que implementen la función. Pero la buena noticia es: ¡funciona! Entonces, de alguna manera, tienes un método crudo para desarrollar tu interfaz a lo largo del tiempo.

Otra cosa interesante acerca de las interfaces virtuales puras: ¡puedes heredar tantas interfaces como quieras y nunca te encontrarás con el problema de los diamantes!

Yo diría que la mayor desventaja de este enfoque es que debe tener mucho cuidado con los tipos que pasa como parámetros. Sin clases ni objetos STL sin antes envolverlos con interfaces virtuales puras. Sin estructuras (sin pasar por el vudú del paquete pragma). Solo tipos primitivos y punteros a otras interfaces. Además, no se pueden sobrecargar las funciones, lo que es un inconveniente, pero no un stop-stopper.

La buena noticia es que con un puñado de líneas de código puede crear clases e interfaces genéricas reutilizables para ajustar series AWL, vectores y otras clases de contenedor. Alternativamente, puede agregar funciones a su interfaz como GetCount () y GetVal (n) para que las personas puedan recorrer las listas.

Las personas que crean complementos para nosotros lo encuentran bastante fácil. No tienen que ser expertos en el límite ABI ni nada, simplemente heredan las interfaces que les interesan, codifican las funciones que respaldan y devuelven falso para las que no.

La tecnología que hace que todo este trabajo no se base en ningún estándar, hasta donde yo sé. Por lo que veo, Microsoft decidió hacer sus tablas virtuales de esa manera para que pudieran hacer COM, y otros escritores de compiladores decidieron hacer lo mismo. Esto incluye a GCC, Intel, Borland y la mayoría de los otros compiladores principales de C ++. Si planea utilizar un compilador incrustado oscuro, entonces este enfoque probablemente no funcione para usted. Teóricamente, cualquier compañía compiladora podría cambiar sus tablas virtuales en cualquier momento y romper las cosas, pero teniendo en cuenta la gran cantidad de código escrito a lo largo de los años que depende de esta tecnología, me sorprendería mucho si alguno de los principales jugadores decidiera romper el rango.

Así que la moraleja de la historia es ... Con la excepción de algunas circunstancias extremas, necesita una persona a cargo de las interfaces que pueda asegurarse de que el límite ABI se mantenga limpio con los tipos primitivos y evite la sobrecarga. Si está de acuerdo con esa estipulación, entonces no tendría miedo de compartir interfaces con clases en DLL / SO entre compiladores. Compartir clases directamente == problema, pero compartir interfaces virtuales puras no es tan malo.