views:

365

answers:

6

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

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.

A: 

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.

Ori Pessach
Actually, Delphi supports the standard ABI calling conventions just fine. It's just that *thiscall* isn't one of them.
Mason Wheeler
-1 for "standardized anything", +1 for both sides supporting COM (hey; that counts as a standard, right?).
Jeroen Pluimers
Hey, I like Delphi as much as the next guy - probably more so, since I've actually used it and thought it was an excellent tool. But the Pascal dialect it implements isn't standardized in the way that, say, C++ is. Delphi uses its own library format, its own object format, and its implementers are free to define the ABI, calling conventions or pretty much anything else in any way they choose. A great tool? Definitely. A standards based environment? Nope.
Ori Pessach
@Jeroen Pluimers - COM counts as an ad-hoc standard, perhaps... Compare that to the way something like Ant is used in the Java world. IDEs that support Ant (all of them, I'm pretty sure) can be used to build anything that uses Ant. Having a well defined standard allows that interoperability. Does it make for a pleasant experience? Not in my opinion. I'd take Delphi's IDE over a standard Java IDE any day, as long as I accept that I'd have to stick with Delphi for developing my project.
Ori Pessach
Same story with many tool vendors: sometimes their tools come before a standard was set, and sometimes they are not large enough to set a standard, but still have a big influence on other standards to be developed. And sometimes they are way ahead of their time. Think about 'the network is the computer' compared to the cloud computing thing. Many tools (including VB6 and Delphi 3) were developed specifically to adhere to the COM standard. In .NET 4.0, COM has become a first class citizen! Most of the Windows API still uses a calling convention introduced long ago by a.o. Anders Hejlsberg.
Jeroen Pluimers
+3  A: 

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;
Serg
The fact that I am seeing setLabel() being called before init() leads me to wonder if the VMT map is off somehow. I hope that is not the case.
Alan G.
@Alan G.: Just for information - The things like "lite COM-like interfaces" are implemented in Delphi using interfaces, not abstract classes. The is a difference between Delphi and C++ here - the interfaces in Delphi are not classes, it is a separate concept. Delphi classes does not support multiple inheritance but can implement multiple interfaces.
Serg
@Serg, you can implement COM interfaces with Delphi classes, too. That's what Delphi 2 did, after all. You can even implement them with plain old records. That's what C does.
Rob Kennedy
@Alan, the reason you're seeing methods called incorrectly is that Delphi's destructor does not live at a positive offset in the VMT. The C++ code expects the destructor to be the first virtual method, the one at offset 0 in the VMT, so you need to declare a new method for that purpose. The Delphi destructor is at offset -4.
Rob Kennedy
@Serg, yeah by COM-like/lite I was referring more to the ABI side i.e matching up VMT entries based on order. I tried your answer by the way, but it does not work, although the logic seems great to me; it seems like a good overall direction. I can see that ECX contains the Self/this pointer, but I get an access violation after entry into Proc from ProcProxy just after *jmp Proc*.
Alan G.
@Alan G.: I have located a bug and updated the post. I have tested the code on Delphi side - it works OK now.
Serg
Seems to work well now; thanks very much. Now however, I get an access violation at the end, tracked it down to the C++ side which does a *delete* on the object, obviously *not* a good thing. Don't know how I can get around this new problem if I am passing a Delphi object over.
Alan G.
A: 

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.

Adam Rosenfield
A: 

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.

rep_movsd
I have compiled major libraries in the past with C++ Builder that were never designed to be (like this one), and the effort was immense vs the results. Chasing down obscure compiler and linker messages and fixing MS-specific code is way less fun than trying to find a more direct (hopefully reasonable) solution to this issue. I know the fix will probably end up being less than standard/clean, but if it *works*, I'll be more than happy.
Alan G.
C++ Builders language compliance has increased vastly from C++ Builder 6 days, perhaps it will work out much easier.maybe a cleaner approach than trying to patch the calling convention by manipulating the stack in assembler.
rep_movsd
A: 

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.

Alexander
A: 

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.

Patrick Martin