将Delphi类传递给期望具有__thiscall方法的类的C ++函数/方法

时间:2010-04-19 13:57:18

标签: c++ delphi dll calling-convention

我有一些MSVC ++编译的DLL,我已经创建了类似COM(lite)的接口(抽象的Delphi类)。其中一些类具有需要指向对象的指针的方法。这些C ++方法是使用 __ thiscall 调用约定(我无法更改)声明的,就像__stdcall一样,除了这个指针传递在ECX注册表上。

我在Delphi中创建类实例,然后将其传递给C ++方法。我可以在Delphi中设置断点并看到它在我的Delphi类中遇到暴露的__stdcall方法,但很快我得到一个STATUS_STACK_BUFFER_OVERRUN并且应用程序必须退出。是否可以在Delphi方面模拟/处理__thiscall?如果我传递一个由C ++系统实例化的对象,那么一切都很好,并调用该对象的方法(如预期的那样),但这没用 - 我需要传递Delphi对象。

  

编辑2010-04-19 18:12 这是更详细的内容:第一个   调用的方法(setLabel)退出时没有   错误(尽管它是一个存根方法)。该   第二种方法叫做(init),进入   然后当它试图读取时死亡    vol 参数。

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;

我有一半期望单独使用stdcall工作,但有些东西搞砸了,不确定是什么,或许与使用ECX寄存器有关?非常感谢帮助。

  

编辑2010-04-19 17:42 可能是ECX注册需要的   入境时保存并恢复一次   功能退出?是这个   C ++需要什么指针?我可能   刚刚达到目前的基础上   一些激烈的谷歌搜索。一世   找到something related,但它   似乎正在处理相反的问题   这个问题。

6 个答案:

答案 0 :(得分:3)

让我们假设您已经创建了一个带有VMT的MSVC ++类,它完美地映射到Delphi类的VMT(我从未做过,我只相信您是可能的)。现在你可以从MSVC ++代码调用Delphi类的虚方法,唯一的问题是__thiscall调用约定。由于Delphi不支持__thiscall,可能的解决方案是在Delphi端使用代理虚方法:

<强>已更新

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;

答案 1 :(得分:1)

我认为你不能合理地期望这种方法有效。 C ++没有标准化的ABI,Delphi没有标准化的任何东西。您可能会找到一种方法来破解可行的方法,但无法保证它将继续使用未来版本的Delphi。

如果您可以修改MSVC方面的东西,您可以尝试使用COM(这正是COM的目的。)这将是丑陋和令人不快的,但我真的没有意识到你很开心现在......所以也许这会有所改善。

如果你不能这样做,看起来你要么必须编写一个thunking层,要么不使用Delphi。

答案 2 :(得分:1)

不要这样做

正如Ori所说,C ++ ABI没有标准化。你不能也不应该期望这种方法能够发挥作用,如果你确实有所管理,那将是一个令人难以置信的非便携式黑客攻击。

跨语言边界引导C ++函数调用的标准方法是使用静态函数,显式传入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
};

(实际上,静态方法在技术上应该声明为extern "C",因为静态类方法不能保证用C ABI实现,但几乎所有编译器都这样做。)

我根本不了解Delphi,所以我不知道在Delphi方面处理这个问题的正确方法是什么,但这是你需要在C ++方面做的事情。如果Delphi支持cdecl调用约定,那么您可以删除上面的STDCALL

是的,这很烦人,你必须从Delphi调用CBox_init而不是init,但这只是你需要处理的事情。当然,如果您愿意,可以将CBox_init重命名为更合适的内容。

答案 3 :(得分:0)

您可以尝试使用C ++ Builder编译这些DLL,C ++ Builder支持与Delphi的互操作性语言支持。自BDS 2006版本以来,可以在Delphi中访问C ++ Builder中创建的组件,因此普通的旧类可以正常工作。

如果您打算仅使用MSVC,COM可能是两种环境之间接口的最佳方式。

答案 4 :(得分:0)

  

我创建了COM-like(lite)接口(抽象Delphi类)

为什么不使用常用的COM接口?在C ++和Delphi中,Thay保证是二进制兼容的。

唯一的问题是你无法避免Delphi中的AddRef / Release / QueryInterface。但是如果你将引用计数实现为什么都不做(如TComponent那样) - 那么你可以忽略C ++方面的这些方法。

答案 5 :(得分:0)

作为使用c ++ Builder建议的补充,由于版本/“构建人”异议的预算/可用性,这可能是一个问题

我建议使用一个简单的MSVC包装器来传递对Delphi dll的调用。 您可以选择是否使用COM。

您可以使用您编写的现有代码,而无需深入研究汇编程序以修复调用约定不匹配。