c++ - programacion - Dll compatibilidad entre compiladores
programar en c online (9)
Casi seguro que está pidiendo problemas si hace esto, mientras que otros comentaristas tienen razón en que el C ++ ABI puede ser el mismo en algunos casos, las dos bibliotecas usan CRT diferentes, diferentes versiones del STL, diferentes semánticas de lanzamiento de excepción, diferentes optimizaciones ... te estás dirigiendo hacia la locura.
¿Hay alguna manera de hacer que los dlls de c ++ construidos con diferentes compiladores sean compatibles entre sí? Las clases pueden tener métodos de fábrica para la creación y destrucción, por lo que cada compilador puede usar su propio nuevo / eliminar (ya que los diferentes tiempos de ejecución tienen sus propios montones).
Intenté con el siguiente código pero se bloqueó en el primer método de miembro:
interfaz.h
#pragma once
class IRefCounted
{
public:
virtual ~IRefCounted(){}
virtual void AddRef()=0;
virtual void Release()=0;
};
class IClass : public IRefCounted
{
public:
virtual ~IClass(){}
virtual void PrintSomething()=0;
};
test.cpp compilado con VC9, test.exe
#include "interface.h"
#include <iostream>
#include <windows.h>
int main()
{
HMODULE dll;
IClass* (*method)(void);
IClass *dllclass;
std::cout << "Loading a.dll/n";
dll = LoadLibraryW(L"a.dll");
method = (IClass* (*)(void))GetProcAddress(dll, "CreateClass");
dllclass = method();//works
dllclass->PrintSomething();//crash: Access violation writing location 0x00000004
dllclass->Release();
FreeLibrary(dll);
std::cout << "Done, press enter to exit." << std::endl;
std::cin.get();
return 0;
}
a.cpp compilado con g ++ g ++. exe -shared c.cpp -o c.dll
#include "interface.h"
#include <iostream>
class A : public IClass
{
unsigned refCnt;
public:
A():refCnt(1){}
virtual ~A()
{
if(refCnt)throw "Object deleted while refCnt non-zero!";
std::cout << "Bye from A./n";
}
virtual void AddRef()
{
++refCnt;
}
virtual void Release()
{
if(!--refCnt)
delete this;
}
virtual void PrintSomething()
{
std::cout << "Hello World from A!" << std::endl;
}
};
extern "C" __declspec(dllexport) IClass* CreateClass()
{
return new A();
}
EDITAR: Agregué la siguiente línea al método GCC CreateClass, el texto se imprimió correctamente en la consola, por lo que su llamada de función es la que lo está matando.
std::cout << "C.DLL Create Class" << std::endl;
Me preguntaba cómo COM logra mantener la compatibilidad binaria incluso en todos los idiomas, ya que básicamente todas las clases tienen herencia (aunque solo una) y, por lo tanto, funciones virtuales. No me molestan masivamente si no puedo haber sobrecargado operadores / funciones, siempre y cuando pueda mantener las cosas básicas de OOP (es decir, clases y herencia única).
Creo que este artículo de MSDN te resultará útil
De todos modos, de un rápido vistazo a su código, puedo decirle que no debe declarar un destructor virtual en una interfaz. En su lugar, debe delete this
en A :: Release () cuando el recuento de ref se reduce a cero.
Debería poder mezclar módulos construidos con diferentes compiladores si reduce sus expectativas y se adhiere a funciones simples.
La forma en que se comportan las clases y las funciones virtuales está definida por el estándar de C ++, pero la forma en que se implementa depende del compilador. En este caso, sé que VC ++ construye objetos que tienen funciones virtuales con un puntero "vtable" en los primeros 4 bytes del objeto (supongo que son 32 bits), y eso apunta a una tabla de punteros a la entrada del método puntos.
Entonces la línea: dllclass->PrintSomething();
es en realidad equivalente a algo así como:
struct IClassVTable {
void (*pfIClassDTOR) (Class IClass * this)
void (*pfIRefCountedAddRef) (Class IRefCounted * this);
void (*pfIRefCountedRelease) (Class IRefCounted * this);
void (*pfIClassPrintSomething) (Class IClass * this);
...
};
struct IClass {
IClassVTable * pVTab;
};
(((struct IClass *) dllclass)->pVTab->pfIClassPrintSomething) (dllclass);
Si el compilador g ++ está implementando las tablas de funciones virtuales de forma diferente a MSFT VC ++, ya que es libre de hacerlo y sigue siendo conforme con el estándar C ++, esto simplemente se bloqueará como lo ha demostrado. El código de VC ++ espera que los punteros de función estén en lugares específicos de la memoria (relativos al puntero del objeto).
Se vuelve más complicado por herencia, y realmente, realmente, complicado con herencia múltiple y herencia virtual.
Microsoft ha sido muy público acerca de la forma en que VC ++ implementa las clases, por lo que puede escribir el código que depende de él. Por ejemplo, muchos encabezados de objetos COM distribuidos por MSFT tienen enlaces C y C ++ en el encabezado. Los enlaces C exponen su estructura vtable como lo hace mi código anterior.
Por otro lado, GNU - IIRC - ha dejado abierta la opción de utilizar diferentes implementaciones en diferentes lanzamientos, y simplemente garantizar que los programas creados con su compilador (¡solo!) Se ajustarán al comportamiento estándar,
La respuesta corta es apegarse a funciones simples de estilo C, estructuras POD (datos antiguos simples, es decir, sin funciones virtuales) y punteros a objetos opacos.
Depende críticamente de que el diseño de la tabla v sea compatible entre VC y GCC. Es probable que esté bien. Asegurarse de que la convención de llamadas coincida es algo que debe verificar (COM: __stdcall, you: __thiscall).
Significativamente, obtiene un AV al escribir. No se escribe nada cuando se llama al método por sí mismo, por lo que es probable que el operador << esté haciendo el bombardeo. ¿Es probable que std :: cout se inicialice por el tiempo de ejecución de GCC cuando se carga una DLL con LoadLibrary ()? El depurador debería decir.
Una forma en que puede organizar el código es usar clases tanto en la aplicación como en el dll, pero mantenga la interfaz entre las dos como funciones externas "C". Esta es la forma en que lo hice con C ++ dlls utilizados por los ensamblajes de C #. Las funciones DLL exportadas se usan para manipular instancias accesibles a través de métodos static class * Instance () como este:
__declspec(dllexport) void PrintSomething()
{
(A::Instance())->PrintSometing();
}
Para múltiples instancias de objetos, haga que una función dll cree la instancia y devuelva el identificador, que luego se puede pasar al método Instance () para usar el objeto específico necesario. Si necesita herencia entre la aplicación y el dll, cree una clase en el lado de la aplicación que ajuste las funciones dll exportadas y obtenga sus otras clases de esa. Organizar su código de este modo mantendrá la interfaz DLL simple y portátil entre compiladores e idiomas.
interesante ... ¿qué pasa si también compila el dll en VC ++, y qué pasa si coloca algunas sentencias de depuración en CreateClass ()?
Diría que es posible que tus 2 "versiones" de cout diferentes en tiempo de ejecución entren en conflicto en lugar de llamar a tu método, pero confío en que la función devuelta puntero / dllclass no sea 0x00000004.
Tu problema es mantener ABI. Aunque es el mismo compilador pero tiene diferentes versiones, aún desea mantener el ABI. COM es una forma de resolverlo. Si realmente quiere entender cómo COM resuelve esto, consulte este artículo CPP a COM en msdn que describe la esencia de COM.
Además de COM, hay otras formas (una de las más antiguas) de resolver ABI, como usar datos antiguos simples y punteros opacos. Mire la manera en que los desarrolladores de bibliotecas de Qt / KDE resuelven ABI.
Puedes utilizar siempre que uses funciones extern "C"
.
Esto se debe a que el ABI "C" está bien definido, mientras que el ABI de C ++ no está definido deliberadamente. Por lo tanto, cada compilador puede definir su propio compilador.
En algunos compiladores, el C ++ ABI entre diferentes versiones del compilador o incluso con banderas diferentes generará un ABI incompatible.
El problema con el código que causa el bloqueo es los destructores virtuales en la definición de la interfaz:
virtual ~IRefCounted(){}
...
virtual ~IClass(){}
Eliminarlos y todo va a estar bien. El problema está causado por la forma en que se organizan las tablas de funciones virtuales. El compilador de MSVC ignora el destructor, pero GCC lo agrega como una primera función en la tabla.
Eche un vistazo a las interfaces COM. No tienen constructores / destructores. Nunca definas ningún destructor en la interfaz y va a estar bien.