6

COM に似た (lite) インターフェイス (抽象 Delphi クラス) を作成した MSVC++ コンパイル済み DLL がいくつかあります。これらのクラスの一部には、オブジェクトへのポインターを必要とするメソッドがあります。これらの C++ メソッドは、__thiscall呼び出し規約 (私は変更できません) で宣言されます。これは、 thisポインターが ECX レジスターで渡されることを除いて、__stdcall と同じです。

Delphi でクラス インスタンスを作成し、それを C++ メソッドに渡します。Delphi でブレークポイントを設定し、Delphi クラスで公開されている __stdcall メソッドにヒットするのを確認できますが、すぐに STATUS_STACK_BUFFER_OVERRUN が発生し、アプリを終了する必要があります。Delphi 側で __thiscall をエミュレート/処理することは可能ですか? C++ システムによってインスタンス化されたオブジェクトを渡すと、すべて問題なく、そのオブジェクトのメソッドが (予想どおり) 呼び出されますが、これは役に立ちません。Delphi オブジェクトを渡す必要があります。

Edit 2010-04-19 18:12これは、より詳細に何が起こるかです: (setLabel) と呼ばれる最初のメソッドは、エラーなしで終了します (ただし、スタブ メソッド)。(init) と呼ばれる 2 番目のメソッドは、 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 レジスタと関係があるのでしょうか? 助けていただければ幸いです。

Edit 2010-04-19 17:42 ECX レジスタをエントリ時に保存し、関数が終了したら復元する必要があるのでしょうか? thisポインターは C++ で必要ですか? 私はおそらく、いくつかの激しいGoogle検索に基づいて、現時点でたどり着いています. 関連するものを見つけましたが、この問題の逆を扱っているようです。

4

6 に答える 6

3

Delphi クラスの VMT に完全にマップされる VMT を使用して MSVC++ クラスを作成したと仮定しましょう (私はそれを行ったことはありませんが、それが可能であると信じています)。これで、MSVC++ コードから Delphi クラスの仮想メソッドを呼び出すことができます。唯一の問題は、__thiscall 呼び出し規約です。__thiscall は Delphi ではサポートされていないため、考えられる解決策は、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の将来のバージョンで動作し続けるという保証はありません。

COMを使用して試すことができるもののMSVC側を変更できる場合(これはまさにCOMが行うように設計されたものです)、それは醜くて不快ですが、私はあなたが今楽しんでいるという感覚を本当に理解していません。 。だから多分それは改善になるでしょう。

それができない場合は、サンクレイヤーを作成するか、Delphiを使用しない必要があるようです。

于 2010-04-19T15:52:34.793 に答える
1

これをしないでください

Ori が述べたように、C++ ABI は標準化されていません。これが機能することを期待することはできませんし、期待すべきではありません。

this言語の境界を越えて C++ 関数呼び出しをブートストラップする標準的な方法は、パラメーターを明示的に渡す静的関数を使用することです。

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はい、これは Delphi からではなく呼び出さなければならないという点で面倒ですが、それinitは対処しなければならないことです。CBox_initもちろん、必要に応じて、より適切な名前に変更できます。

于 2010-04-19T16:07:07.523 に答える
0

COM のような (ライト) インターフェイス (抽象 Delphi クラス) を作成しました。

通常の COM インターフェイスを使用しないのはなぜですか。Thay は、C++ および Delphi でバイナリ互換性があることが保証されています。

唯一の問題は、Delphi では AddRef/Release/QueryInterface を回避できないことです。しかし、(TComponent が行うように) 参照カウントを何もしないように実装する場合は、C++ 側からこれらのメソッドを無視することができます。

于 2010-04-20T07:14:57.153 に答える
0

c++Builder の使用に関する提案の補足として。これは、予算/バージョンの入手可能性/「ビルド ガイ」の異議のために問題となる可能性があります。

Delphi dll(s) への呼び出しを渡す単純な MSVC ラッパーをお勧めします。その時点で、COM を使用するかどうかを選択できます。

呼び出し規約の不一致を修正するためにアセンブラーを掘り下げることなく、作成した既存のコードをほとんどそのまま使用できます。

于 2010-08-09T15:17:42.287 に答える
0

代わりに、C++ Builder でこれらの DLL をコンパイルしてみてください。C++ Builder には、Delphi との相互運用性のための言語サポートがあります。BDS 2006 バージョン以降、C++Builder で作成されたコンポーネントに Delphi でアクセスできるようになったため、プレーンな古いクラスでも問題なく動作します。

MSVC のみを使用する場合は、COM がおそらく 2 つの環境間のインターフェイスとして最適な方法です。

于 2010-04-19T16:21:47.620 に答える