6

我有一些 MSVC++ 编译的 DLL,为此我创建了类似 COM(lite)的接口(抽象 Delphi 类)。其中一些类具有需要指向对象的指针的方法。这些 C++ 方法使用__thiscall调用约定(我无法更改)声明,这就像 __stdcall 一样,除了this指针在 ECX 寄存器上传递。

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

编辑 2010-04-19 18:12这是更详细的情况:第一个名为 (setLabel) 的方法退出时没有错误(尽管它是一个存根方法)。第二种方法称为 (init),当它尝试读取 vol参数时进入然后死掉。

C++ 端

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

德尔福侧

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++ 需要this指针吗?我现在可能只是根据一些激烈的谷歌搜索到达。我发现了一些相关的东西,但它似乎正在处理这个问题的反面。

4

6 回答 6

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;
于 2010-04-19T16:05:31.663 回答
1

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

如果您可以修改 MSVC 方面的事情,您可以尝试使用 COM(这正是 COM 的设计目的。)这将是丑陋和不愉快的,但我真的不明白你现在玩得开心.. . 所以也许这将是一个改进。

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

于 2010-04-19T15:52:34.793 回答
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上述内容。

是的,这很烦人,因为您必须调用CBox_init而不是init从 Delphi 调用,但这只是您必须处理的事情。当然,您可以根据需要重命名CBox_init为更合适的名称。

于 2010-04-19T16:07:07.523 回答
0

您可以尝试使用 C++ Builder 编译这些 DLL,C++ Builder 具有与 Delphi 互操作性的语言支持。从 BDS 2006 版本开始,用 C++Builder 制作的组件可以在 Delphi 中访问,所以普通的旧类可以正常工作。

如果您打算只使用 MSVC,那么 COM 可能是在这两种环境之间进行交互的最佳方式。

于 2010-04-19T16:21:47.620 回答
0

我创建了类 COM(精简版)接口(抽象 Delphi 类)

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

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

于 2010-04-20T07:14:57.153 回答
0

作为使用 c++Builder 建议的补充,这可能是由于版本的预算/可用性/“build guy”反对而导致的问题

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

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

于 2010-08-09T15:17:42.287 回答