2009-01-13 13 views
7

¿Hay alguna manera de hacer que los dlls C++ construidos con diferentes compiladores sean compatibles entre sí? Las clases pueden tener métodos de fábrica para creación y destrucción, por lo que cada compilador puede usar su propio nuevo/eliminar (ya que los tiempos de ejecución diferentes tienen sus propios montones).Compatibilidad Dll entre compiladores

He probado el siguiente código pero se estrelló en el primer método de la barra:

interface.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; 
} 

una .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(); 
} 

EDIT: Agregué la siguiente línea al método CreateClass de GCC, 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 gestionar COM para mantener la compatibilidad binaria incluso a través de las lenguas, ya que sus Básicamente todas las clases con inheritence (aunque sólo individual) y, por tanto, las 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).

+0

¿Cómo lo hace COM? Con llamadas RPC livianas, podría construir su aplicación usando dce-rpc y obtendría los mismos resultados. En ningún caso, COM proporciona un puntero a la memoria de un dll externo, realiza llamadas de función a ese dll. – gbjbaanb

+0

Los siguientes artículos [aquí] (http://eli.thegreenplace.net/2011/09/16/exporting-c-classes-from-a-dll/), [aquí] (http: //www.codeproject. com/Articles/28969/HowTo-Export-C-classes-from-a-DLL # CppMatureApproach), y [aquí] (http://chadaustin.me/cppinterface.html) pueden ser útiles. El ejemplo de su código casi está disponible, excepto el destructor virtual integrado. AFAIK, todos los métodos en su interfaz abstracta deben ser puros virtuales '= 0'. – greatwolf

Respuesta

8

Debería ser capaz de 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.

Así la línea: dllclass->PrintSomething(); es en realidad equivalente a algo 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 cualquier manera diferente a MSFT VC++ - ya que es libre de hacer y seguir cumpliendo con el estándar de 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 sobre la forma en que VC++ implementa las clases, por lo que puede escribir el código que depende de ello. 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 usar diferentes implementaciones en diferentes versiones, y simplemente garantizar los programas incorporados con su compilador (Sólo) se adaptará 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.

+0

Preferiría no tener que usar simplemente funciones sencillas en todas partes, simplemente les digo a todos que deben compilar el dll con VC9 antes de dar ese paso ... –

+0

Es posible que pueda hacer lo que desee con CORBA, pero no lo hago saber mucho al respecto –

0

interesante ... ¿Qué sucede 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 sus 2 'versiones' de cout diferentes en tiempo de ejecución entren en conflicto en lugar de la llamada a su método, pero confío en que la función devuelta puntero/dllclass no sea 0x00000004?

+0

No, el depurador de VC mostró un valor razonable para el puntero, también, por lo que puedo decir (nunca he tratado de depurar un binario sin VC con VC antes) nunca entró realmente en el método PrintSomething, al menos el Marco de pila indica nunca ingresó al dll en este punto. –

+0

Cuando está depurando un código que no se creó con VC, o incluso cuando está pero no tiene los símbolos de depuración, no puede confiar completamente en lo que el depurador le está diciendo sobre la pila de llamadas. –

2

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 realiza la llamada al método en sí, 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.

+0

¿Cómo puedo verificar si se creó correctamente en el dll de GCC usando VC? Además, ¿cómo funciona COM a través de __stdcall, ya que incluso las clases básicas tenían que funcionar a través de __stiscall en VC? –

2

Puede usar siempre las funciones extern "C".

Esto se debe a que la "C" ABI está bien definida, mientras que la C++ ABI deliberadamente no está definida. Por lo tanto, cada compilador puede definir su propio compilador.

En algunos compiladores, C++ ABI entre diferentes versiones del compilador o incluso con banderas diferentes generará un ABI incompatible.

+0

voy por http://en.wikipedia.org/wiki/Application_binary_interface –

+0

@Shy: Application Binary Interface. –

3

creo que encontrará this MSDN article useful

De todos modos, a partir de un rápido vistazo a su código, te puedo decir que no se debe declarar un destructor virtual en una interfaz. En su lugar, debe hacer delete this dentro de A :: Release() cuando el recuento de ref se reduce a cero.

5

Es casi seguro que está buscando problemas si hace esto, mientras que otros comentaristas están en lo cierto de que el C++ ABI puede ser el mismo en algunos casos, las dos bibliotecas usan CRT diferentes, versiones diferentes del STL, excepción diferente lanzando semántica, diferentes optimizaciones ... te estás dirigiendo hacia la locura.

3

Una forma en que puede organizar el código es usar clases tanto en la aplicación como en el dll, pero mantener 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 utilizan para manipular casos accesibles a través de clase estática * Instancia() métodos como este:

__declspec(dllexport) void PrintSomething() 
{ 
    (A::Instance())->PrintSometing(); 
} 

Para múltiples instancias de objetos, tienen una función DLL crear la instancia y devolver el identificador, que luego puede ser pasado 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.

+0

Ok, ¿hay alguna manera de hacer esto al menos de forma automática, en lugar de escribir como 4 cosas para cada método (cargando el método C para que la interfaz pueda encontrarlo, traduciendo la llamada a la interfaz a un método C, yendo desde Método C a un método OOP en el dll, y finalmente el método dll? –

+0

Podría automatizar parcialmente esto. Debería mantener una lista de archivos de las clases y métodos. Un script podría procesar ese archivo, generando los archivos para ser # incluido donde sea necesario. Sin embargo, tenga en cuenta que las funciones C dll no podrían aceptar directamente parámetros de instancia C++ *. –

0

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 to COM in msdn que describe la esencia de COM.

Aparte de COM, hay otras (una de las más antiguas) formas de resolver ABI como usar datos antiguos simples y punteros opacos. Look en los desarrolladores de la biblioteca Qt/KDE forma de resolver ABI.

0

El problema con el código que hace que el choque es destructores virtuales en la definición de 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.

Cuestiones relacionadas