2010-04-19 17 views
6

Tengo algunas DLL compiladas en MSVC++ para las que he creado interfaces COM (interfaz literaria) (clases abstractas de Delphi). Algunas de esas clases tienen métodos que necesitan punteros a los objetos. Estos métodos C++ se declaran con la convención de llamadas __thiscall (que I no puede cambiar), que es como __stdcall, excepto que este puntero se pasa en el registro ECX.Pasar una clase Delphi a una función/método C++ que espera una clase con __thiscall methods

Creo la instancia de clase en Delphi, luego la paso al método C++. Puedo establecer puntos de interrupción en Delphi y ver cómo golpea los métodos __stdcall expuestos en mi clase Delphi, pero pronto obtengo un STATUS_STACK_BUFFER_OVERRUN y la aplicación tiene que salir. ¿Es posible emular/tratar con __thiscall en el lado Delphi de las cosas? Si paso un objeto instanciado por el sistema C++, entonces todo está bien, y los métodos de ese objeto se llaman (como era de esperar), pero esto es inútil: necesito pasar objetos Delphi.

Editar 2010-04-19 18:12 Esto es lo que ocurre con más detalle: El primer método llamado (setLabel) sale sin error (aunque es un método de talón). El segundo método llamado (init), ingresa y luego muere cuando intenta leer el parámetro vol.

C++ lateral

#define SHAPES_EXPORT __declspec(dllexport) // just to show the value 
class SHAPES_EXPORT CBox 
{ 
    public: 
    virtual ~CBox() {} 
    virtual void init(double volume) = 0; 
    virtual void grow(double amount) = 0; 
    virtual void shrink(double amount) = 0; 
    virtual void setID(int ID = 0) = 0; 
    virtual void setLabel(const char* text) = 0; 
}; 

Delphi lateral

IBox = class 
public 
    procedure destroyBox; virtual; stdcall; abstract; 
    procedure init(vol: Double); virtual; stdcall; abstract; 
    procedure grow(amount: Double); virtual; stdcall; abstract; 
    procedure shrink(amount: Double); virtual; stdcall; abstract; 
    procedure setID(val: Integer); virtual; stdcall; abstract; 
    procedure setLabel(text: PChar); virtual; stdcall; abstract; 
end; 

TMyBox = class(IBox) 
protected 
    FVolume: Double; 
    FID: Integer; 
    FLabel: String; // 
public 
    constructor Create; 
    destructor Destroy; override; 
    // BEGIN Virtual Method implementation 
    procedure destroyBox; override; stdcall;    // empty - Dont need/want C++ to manage my Delphi objects, just call their methods 
    procedure init(vol: Double); override; stdcall;  // FVolume := vol; 
    procedure grow(amount: Double); override; stdcall; // Inc(FVolume, amount); 
    procedure shrink(amount: Double); override; stdcall; // Dec(FVolume, amount); 
    procedure setID(val: Integer); override; stdcall; // FID := val; 
    procedure setLabel(text: PChar); override; stdcall; // Stub method; empty. 
    // END Virtual Method implementation  
    property Volume: Double read FVolume; 
    property ID: Integer read FID; 
    property Label: String read FLabel; 
end; 

que tendría la mitad espera que el uso de stdcall solo para trabajar, pero algo es echar a perder, no está seguro de lo que, tal vez algo que ver con el registro ECX que se está utilizando? La ayuda sería muy apreciada.

Editar 2010-04-19 17:42 Podría ser que el registro ECX necesita ser preservado a la entrada y una vez restaurado se cierra la función? ¿Es el este puntero requerido por C++? Probablemente estoy acaba de llegar en este momento basado en algunas búsquedas intensas de Google. I encontró something related, pero parece estar tratando con el inverso de este problema.

+0

Usted dice que "pronto" obtiene un error STATUS_STACK_BUFFER_OVERRUN. ¿Que tan pronto? ¿Puedes publicar un código de muestra para mostrar dónde ocurre el error? ¿Sucede en todos los métodos? (¿Has probado todos los métodos?) –

+0

@Mason, la persona que llama está poniendo argumentos 'N' en la pila, incluido' this'. El receptor solo está eliminando los argumentos 'N-1' porque cree que' this' está en el registro ECX, no en la pila. Eso lleva a un desbordamiento de pila eventualmente. –

+0

El primer método llamado (setLabel) sale sin error (aunque es un método stub). El segundo método llamado (init), ingresa y luego muere cuando intenta leer el parámetro * vol *. – Atorian

Respuesta

3

Supongamos que ha creado una clase MSVC++ con VMT que se adapta perfectamente al VMT de una clase Delphi (nunca lo he hecho, solo creo que es posible). Ahora puede llamar a los métodos virtuales de una clase Delphi desde el código MSVC++, el único problema es ___esta llamada a la convención de llamadas. Desde __thiscall no es compatible con Delphi, la posible solución es utilizar métodos virtuales de proxy en el lado Delphi:

ACTUALIZADO

type 
    TTest = class 
    procedure ECXCaller(AValue: Integer); 
    procedure ProcProxy(AValue: Integer); virtual; stdcall; 
    procedure Proc(AValue: Integer); stdcall; 
    end; 

implementation 

{ TTest } 

procedure TTest.ECXCaller(AValue: Integer); 
asm 
    mov ecx,eax 
    push AValue 
    call ProcProxy 
end; 

procedure TTest.Proc(AValue: Integer); 
begin 
    ShowMessage(IntToStr(AValue)); 
end; 

procedure TTest.ProcProxy(AValue: Integer); 
asm 
    pop ebp   // !!! because of hidden delphi prologue code 
    mov eax,[esp]  // return address 
    push eax 
    mov [esp+4],ecx // "this" argument 
    jmp Proc 
end; 
+0

El hecho de que vea que setLabel() se llama antes de init() me lleva a preguntarme si el mapa de VMT está desactivado de alguna manera. Espero que ese no sea el caso. – Atorian

+0

@ Alan G .: Solo para información: las cosas como "interfaces tipo lite COM" se implementan en Delphi utilizando interfaces, no clases abstractas. Aquí hay una diferencia entre Delphi y C++: las interfaces en Delphi no son clases, es un concepto separado. Las clases de Delphi no admiten herencia múltiple pero pueden implementar múltiples interfaces. – kludg

+1

@Serg, también puede implementar interfaces COM con clases Delphi. Eso es lo que hizo Delphi 2, después de todo. Incluso puede implementarlos con registros antiguos simples. Eso es lo que C hace. –

1

No creo que pueda razonablemente esperar que esto funcione. C++ no tiene un ABI estandarizado, y Delphi no tiene nada estandarizado. Puede encontrar una forma de piratear algo que funciona, pero no hay garantía de que continuará trabajando con futuras versiones de Delphi.

Si puede modificar el lado MSVC de las cosas, podría intentar usar COM (esto es exactamente lo que COM fue diseñado para hacer). Será feo y desagradable, pero realmente no entiendo lo que está teniendo divertido ahora ... Entonces tal vez eso sería una mejora.

Si no puede hacer eso, parece que tendrá que escribir una capa thunk o no usar Delphi.

+3

En realidad, Delphi es compatible con las convenciones estándar de llamadas ABI. Es solo que * este llamado * no es uno de ellos. –

+1

-1 para "nada estandarizado", +1 para ambos lados compatible con COM (hey, eso cuenta como estándar, ¿verdad?). –

+0

Oye, me gusta Delphi tanto como el próximo chico, probablemente más aún, ya que en realidad lo he usado y pensé que era una excelente herramienta. Pero el dialecto de Pascal que implementa no está estandarizado en la forma en que, por ejemplo, C++. Delphi usa su propio formato de biblioteca, su propio formato de objeto, y sus implementadores son libres de definir el ABI, las convenciones de llamadas o casi cualquier otra cosa de la forma que elijan. Una gran herramienta? Seguro. ¿Un entorno basado en estándares? Nop. –

1

No haga esto.

Como mencionó Ori, el C++ ABI no está estandarizado. No puede y no debe esperar que esto funcione, y si logra algo, será un hack increíblemente no portátil.

La forma estándar de arranque C++ función llama a través de los límites del lenguaje es el uso de funciones estáticas donde se pasa de forma explícita en un parámetro this:

class SHAPES_EXPORT CBox 
{ 
    public: 
    virtual void init(double volume) = 0; 
    static void STDCALL CBox_init(CBox *_this, double volume) { _this->init(volume); } 
    // etc. for other methods 
}; 

(En realidad, el método estático debería técnicamente sea declarado extern "C", ya estática No se garantiza la implementación de métodos de clase con el C ABI, pero prácticamente todos los compiladores existen.)

No conozco Delphi para nada, así que no sé cuál es la forma correcta de manejarlo esto en el lado Delphi de las cosas es, pero esto es lo que necesitas d a hacer en el lado de C++. Si Delphi admite la convención de llamadas cdecl, entonces puede eliminar el STDCALL anterior.

Sí, esto es molesto en el que usted tiene que llamar CBox_init en lugar de init de Delphi, pero eso es sólo algo que se tiene que tratar. Puede cambiar el nombre de CBox_init a algo más adecuado si lo desea, por supuesto.

0

Puede intentar compilar esas DLL con C++ Builder en su lugar, C++ Builder tiene soporte de lenguaje para la interoperabilidad con Delphi. Desde la versión BDS 2006, se puede acceder a los componentes creados en C++ Builder en Delphi, por lo que las clases antiguas simples funcionarían perfectamente.

Si tiene la intención de utilizar solo MSVC, COM es quizás la mejor manera de interactuar entre los dos entornos.

+0

he recopilado las principales bibliotecas en el pasado con C++ Builder que nunca fueron diseñados para ser (como éste), y el esfuerzo fue inmensa vs los resultados. Perseguir el compilador oscuro y los mensajes del enlazador y arreglar el código específico de MS es mucho menos divertido que tratar de encontrar una solución más directa (con suerte razonable) a este problema. Sé que la solución probablemente será menos que estándar/limpio, pero si * funciona *, estaré más que feliz. – Atorian

+0

El cumplimiento del lenguaje C++ Builders ha aumentado enormemente desde C++ Builder 6 días, quizás resulte mucho más fácil. quizás un enfoque más limpio que intentar parchar la convención de llamadas manipulando la pila en el ensamblador. –

0

que he creado (lite) interfaces COM-como (clases abstractas Delphi)

¿Por qué no usar interfaces COM habituales? Se garantiza que serán compatibles binariamente en C++ y Delphi.

El único problema es que no se puede evitar AddRef/Release/QueryInterface en Delphi. Pero si implementa el recuento de referencias como si no hiciera nada (como hace TComponent), entonces puede ignorar estos métodos desde el lado de C++.

0

Como complemento al uso C++ Builder sugerencia, que puede ser un problema debido a un presupuesto/disponibilidad de la versión/"construcción tipo" objeciones

que sugieren un simple envoltorio MSVC que pasa en las llamadas a Delphi dll (s). Puede elegir usar COM o no en ese punto.

Puede utilizar el código existente que ha escrito prácticamente tal como está, sin tener que profundizar en el ensamblador para solucionar los desajustes de las convenciones de llamadas.

Cuestiones relacionadas