开发者

Pass a Delphi class to a C++ function/method that expects a class with __thiscall methods

I have some MSVC++ compiled DLL's for which I have created COM-like (lite) interfaces (abstract Delphi classes). Some of those classes have methods that need pointers to objects. These C++ methods are declared with the __thiscall calling convention (which I cannot change), which is just like __stdcall, except a this pointer is passed on the ECX register.

I create the class instance in Delphi, then pass it on to the C++ method. I can set breakpoints in Delphi and see it hitting the exposed __stdcall methods in my Delphi class, but soon I get a STATUS_STACK_BUFFER_OVERRUN and the app has to exit. Is it possible to emulate/deal with __thiscall on the Delphi side of things? If I pass an object instantiated by the C++ system then all is good, and that object's methods are called (as would be expected), but this is useless - I need to pass Delphi objects.

Edit 2010-04-19 18:12 This is what happens in more detail: The first method called (setLabel) exits with no error (though its a stub method). The second method called (init), enters then dies when it attempts to read the vol parameter.

C++ Side

#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 Side

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: In开发者_JS百科teger); 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;

I would have half expected using stdcall alone to work, but something is messing up, not sure what, perhaps something to do with the ECX register being used? Help would be greatly appreciated.

Edit 2010-04-19 17:42 Could it be that the ECX register needs to be preserved on entry and restored once the function exits? Is the this pointer required by C++? I'm probably just reaching at the moment based on some intense Google searches. I found something related, but it seems to be dealing with the reverse of this issue.


Let us assume that you have created a MSVC++ class with VMT that maps perfectly into the VMT of a Delphi class (I have never done it, I just believe you that is possible). Now you can call the virtual methods of a Delphi class from MSVC++ code, the only problem is __thiscall calling convention. Since __thiscall is not supported in Delphi, the possible solution is to use proxy virtual methods on Delphi side:

UPDATED

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;


I don't think you can reasonably expect this to work. C++ doesn't have a standardized ABI, and Delphi doesn't have a standardized anything. You might find a way to hack something that works, but there's no guarantee it will continue working with future versions of Delphi.

If you can modify the MSVC side of things you could try using COM (this is exactly what COM was designed to do.) It will be ugly and unpleasant, but I really don't get the sense you're having fun now... So maybe that would be an improvement.

If you can't do that, it seems like you'll either have to write a thunking layer or not use Delphi.


Don't do this.

As Ori mentioned, the C++ ABI is not standardized. You cannot and should not expect this to work, and if you do manage something, it will be an incredibly non-portable hack.

The standard way of bootstrapping C++ function calls across language boundaries is to use static functions where you explicitly pass in a this parameter:

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

(Actually, the static method should technically be declared extern "C", since static class methods aren't guaranteed to be implemented with the C ABI, but pretty much all compilers in existence do so.)

I don't know Delphi at all, so I don't know what the proper way to handle this on the Delphi side of things is, but this is what you need to do on the C++ side. If Delphi supports the cdecl calling convention, then you can remove the STDCALL above.

Yes, this is annoying in that you have to call CBox_init instead of init from Delphi, but that's just something you have to deal with. You can rename CBox_init to something more suitable if you like, of course.


You can try compiling those DLLs with C++ Builder instead, C++ Builder has language support for interoperability with Delphi. Since the BDS 2006 version, components made in C++Builder can be accessed in Delphi, so plain old classes would work just fine.

If you intend to use MSVC only, COM is perhaps the best way to interface between the two environments.


I have created COM-like (lite) interfaces (abstract Delphi classes)

Why don't you use usual COM interfaces? Thay are guaranteed to be binary compatitible in C++ and Delphi.

The only problem is that you can't avoid AddRef/Release/QueryInterface in Delphi. But if you'll implement reference counting as doing nothing (as TComponent do) - then you can just ignore these methods from C++ side.


As a complement to the use c++Builder suggestion, which may be a problem due to budget/availability of version/"build guy" objections

I suggest a simple MSVC wrapper that passes on the calls to the Delphi dll(s). You can choose to use COM or not at that point.

You can use the existing code you've written pretty much as-is, without having to delve into assembler to fix up calling conventions mismatches.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜