Передайте класс Delphi в функцию / метод C ++, который ожидает класс с методами __thiscall - PullRequest
6 голосов
/ 19 апреля 2010

У меня есть несколько скомпилированных MSVC ++ DLL, для которых я создал COM-подобные (lite) интерфейсы (абстрактные классы Delphi). Некоторые из этих классов имеют методы, которые нуждаются в указателях на объекты. Эти методы C ++ объявляются с __ thiscall соглашением о вызовах (которое я не могу изменить ), которое аналогично __stdcall, за исключением того, что указатель this передается в ECX регистр.

Я создаю экземпляр класса в Delphi, а затем передаю его в метод C ++. Я могу установить точки останова в Delphi и увидеть, как он воздействует на открытые методы __stdcall в моем классе Delphi, но вскоре я получаю STATUS_STACK_BUFFER_OVERRUN, и приложение должно завершиться. Можно ли эмулировать / иметь дело с __thiscall на стороне Delphi? Если я передаю объект, созданный системой C ++, то все хорошо, и методы этого объекта вызываются (как и следовало ожидать), но это бесполезно - мне нужно передать объекты Delphi.

Редактировать 2010-04-19 18: 12 Вот что происходит более подробно: первый вызываемый метод (setLabel) завершается без ошибка (хотя это метод заглушки). второй метод называется (init), входит затем умирает, когда он пытается прочитать том параметр.

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 ++? Я наверное просто достигнув в данный момент на основе некоторые интенсивные поиски Google. я нашел что-то связанное , но это похоже имеет дело с обратным этого номера.

Ответы [ 6 ]

3 голосов
/ 19 апреля 2010

Давайте предположим, что вы создали класс MSVC ++ с VMT, который идеально отображается в VMT класса Delphi (я никогда не делал этого, я просто верю, что это возможно). Теперь вы можете вызывать виртуальные методы класса Delphi из кода MSVC ++, единственная проблема - __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;
1 голос
/ 19 апреля 2010

Не делайте этого .

Как упоминалось 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 во что-то более подходящее, если хотите, конечно.

1 голос
/ 19 апреля 2010

Не думаю, что вы можете ожидать, что это сработает. C ++ не имеет стандартизированного ABI, а Delphi не имеет ничего стандартизированного. Возможно, вы найдете способ взломать что-то, что работает, но нет гарантии, что оно продолжит работу с будущими версиями Delphi.

Если вы можете изменить сторону MSVC, вы можете попробовать использовать COM (это именно то, для чего предназначен COM). Это будет уродливо и неприятно, но я действительно не понимаю, что вам весело сейчас ... Так что, может быть, это будет улучшение.

Если вы не можете этого сделать, похоже, что вам придется либо написать слой с надписью, либо не использовать Delphi.

0 голосов
/ 09 августа 2010

В качестве дополнения к предложению c ++ Builder, которое может быть проблемой из-за бюджета / наличия версии / возражений "build guy"

Я предлагаю простую оболочку MSVC, которая передает вызовы к Delphi dll (s). Вы можете использовать COM или нет на этом этапе.

Вы можете использовать существующий код, который вы написали, как есть, без необходимости углубляться в ассемблер, чтобы исправить несоответствия между соглашениями о вызовах.

0 голосов
/ 20 апреля 2010

Я создал COM-подобные (lite) интерфейсы (абстрактные классы Delphi)

Почему вы не используете обычные COM-интерфейсы? Они гарантированно совместимы в двоичном коде в C ++ и Delphi.

Единственная проблема в том, что вы не можете избежать AddRef / Release / QueryInterface в Delphi. Но если вы будете реализовывать подсчет ссылок как ничего не делая (как это делает TComponent) - тогда вы можете просто игнорировать эти методы со стороны C ++.

0 голосов
/ 19 апреля 2010

Вместо этого вы можете попробовать скомпилировать эти DLL с помощью C ++ Builder, C ++ Builder имеет языковую поддержку для взаимодействия с Delphi. Начиная с версии BDS 2006, компоненты, созданные в C ++ Builder, могут быть доступны в Delphi, поэтому старые классы будут работать нормально.

Если вы собираетесь использовать только MSVC, COM, возможно, является наилучшим способом взаимодействия двух сред.

...