2016-03-23 2 views
3

Мне нужно вызвать код C++ из Delphi. Код C++ должен иметь возможность обратного вызова в код Delphi в обратном порядке. Пример, показанный здесь Calling a callback function in Delphi from a C++ DLL, работает отлично. Однако вместо того, чтобы переходить на C++ одну функцию Delphi в качестве обратного вызова, я хотел бы передать объект Delphi, реализующий интерфейс.Совместимы ли VMTs класса Delphi и C++?

Редактировать: По интерфейсу Я имею в виду терминологию C++, которая представляет собой класс с чистыми виртуальными функциями. Это не обязательно тип, определенный с помощью ключевого слова Delphi interface. Другими словами, следующий класс определяет интерфейс, я хотел бы позвонить из C++:

ICallable = class 
    procedure callMe stdcall; virtual; abstract; 
    procedure CallMeAgain stdcall; virtual; abstract; 
end; 

Интерфейс ICallable в свою очередь будет реализован в Delphi следующим образом:

MyCallable = class(ICallable) 
    procedure callMe override; 
    procedure callMeAgain override; 
end; 

procedure MyCallable.callMe 
begin 
    WriteLine('I was called'); 
end; 

procedure MyCallable.callMeAgain 
begin 
    WriteLine('I was called again'); 
end; 

На стороне C++, который составляется как DLL, я хочу, чтобы определить ICallable интерфейс следующим образом:

class ICallable{ 
public: 
    virtual void callMe()=0; 
    virtual void callMeAgain()=0; 
} 

И экспортировать функции DLL, так что его можно назвать по Delphi:

#define DllExport extern "C" __declspec(dllexport) 

DLLExport bool Callback(ICallable* callable){ 
    callable->callMe(); 
    callable->callMeAgain(); 
    return true; 
} 

И, наконец, обратно в Delphi:

function Callback(myCallable: ICallable) : Boolean cdecl; external 'dllname' 

Вопрос:

  • Это может работать только если C++ и Delphi реализовать свои виртуальные таблицы методов таким же образом. Это так?
+2

Интерфейсы Delphi следуют соглашениям COM. Поэтому вам нужно следовать соглашениям COM на C++, см. Http://rvelthuis.de/articles/articles-cppobjs.html и http://www.scritub.com/stiinta/tutorials/visual-c-en/Sharing- Code-and-Objects-Betwe18279520.php – Johan

+0

Ох и интерфейсы в Delphi объявляются с использованием ключевого слова 'interface'. Если вы настаиваете на объявлении интерфейса, отличного от COM, вы используете 'abstract class'. Реализация интерфейса происходит из 'TInterfacedObject'. – Johan

+0

@Johan Я пытаюсь явно избегать всего COM-оборудования здесь. А также в моем случае я не проектирую компонент C++ COM. Я хочу иметь возможность обратного вызова к объекту Delphi «совместного использования» того же «интерфейса» с C++. В сущности, я хочу знать, сопоставляет ли класс Delphi виртуальные абстрактные методы с соответствующим классом C++ с чистыми виртуальными функциями. – BigONotation

ответ

11

Это может работать только в C++ и Delphi, реализуя их таблицы виртуальных методов таким же образом. Это так?

Первоначально предполагалось, что класс Delphi не совместим с VMT с классом C++. Я думал, что, поскольку все классы Delphi происходят от TObject, который объявляет виртуальные методы. Эти виртуальные методы появляются в VMT, я предположил, что эти методы появятся сначала в VMT. Однако выясняется, что компилятор устанавливает, что встроенные виртуальные методы TObject имеют отрицательные индексы в VMT. Это означает, что пользовательские виртуальные методы (определяемые в подклассах TObject) начинаются с индекса 0.

Это означает, что классы Delphi и C++ в вашем коде в вопросе действительно имеют совместимые VMT. Я считаю, что этот дизайн был сделан для поддержки COM в более ранних версиях Delphi. Чтобы создать резервную копию своих претензий, я отсылаю вас к documentation говорит, с моим акцентом:

Компоновка в ВМТ показано в следующей таблице. На 32-битных платформах, при положительных смещениях, VMT состоит из списка 32-битных указателей методов (указателей на 64-разрядные методы на 64-битной платформе) - по одному на пользовательский виртуальный метод в классе типа - - в порядке декларации. Каждый слот содержит адрес соответствующей точки входа виртуального метода. Этот макет - , совместимый с C++ v-table и с COM. При отрицательных смещениях VMT содержит ряд полей, которые являются внутренними для реализации Delphi.Приложения должны использовать методы, определенные в TObject, для запроса этой информации, поскольку макет, вероятно, изменится в будущих реализациях языка Delphi.

Следует подчеркнуть, что ничто в стандарте C++ не требует использования виртуальных машин для виртуальных методов, тем более что VMT реализуются. В действительности, каждый основной компилятор Windows имеет VMT, реализованные таким образом, чтобы поддерживать COM.

Вместо того, чтобы полагаться на такие детали реализации, вы можете использовать интерфейсы Delphi. Однако, как вы знаете, это COM-интерфейсы, поэтому вы должны реализовать IUnknown. Вы говорите, что хотите избежать машин COM, но вам нужно добавить только IUnknown. На мой взгляд, это не особенно тяжело. Я понимаю, что вы считаете, что вам нужно регистрировать CLSID, внедрять фабрики классов и так далее. Вы этого не сделаете. Вам просто нужно было бы реализовать IUnknown.

Во всяком случае, если вы действительно настроены на избегая IUnknown, то вы не можете использовать интерфейсы Delphi и имеют два варианта, насколько я могу сказать:

  1. Реализовать VMT вручную в коде Delphi. VMT - это всего лишь массив указателей на функции. Это приведет вас к коду, который выглядит так, как COM делает в C. Совершенно возможно, но не совсем приятно.
  2. Используйте подход, изложенный в вашем вопросе, и полагайтесь на детали реализации, которые TObject использует отрицательные индексы VMT для своих встроенных виртуальных методов.

компилируется для варианта кода 2 выглядит следующим образом:

Delphi

{$APPTYPE CONSOLE} 

type 
    ICallable = class 
    public 
    procedure CallMe cdecl; virtual; abstract; 
    procedure CallMeAgain cdecl; virtual; abstract; 
    end; 

    MyCallable = class(ICallable) 
    public 
    procedure CallMe; override; 
    procedure CallMeAgain; override; 
    end; 

procedure MyCallable.CallMe; 
begin 
    Writeln('CallMe'); 
end; 

procedure MyCallable.CallMeAgain; 
begin 
    Writeln('CallMeAgain'); 
end; 

const 
    dllname = 'C:\Users\heff\Desktop\Win32Project1\Debug\Win32Project1.dll'; 

function Callback(Callable: ICallable): Boolean; cdecl; external dllname; 

var 
    Callable: ICallable; 

begin 
    Callable := MyCallable.Create; 
    Writeln(Callback(Callable)); 
    Callable.Free; 
    Readln; 
end. 

C++

#include <Windows.h> 

BOOL APIENTRY DllMain(HMODULE hModule, 
         DWORD ul_reason_for_call, 
         LPVOID lpReserved 
        ) 
{ 
    switch (ul_reason_for_call) 
    { 
    case DLL_PROCESS_ATTACH: 
    case DLL_THREAD_ATTACH: 
    case DLL_THREAD_DETACH: 
    case DLL_PROCESS_DETACH: 
     break; 
    } 
    return TRUE; 
} 

class ICallable 
{ 
public: 
    virtual void CallMe() = 0; 
    virtual void CallMeAgain() = 0; 
}; 

extern "C" __declspec(dllexport) bool Callback(ICallable* callable) 
{ 
    callable->CallMe(); 
    callable->CallMeAgain(); 
    return true; 
} 

Выходной

 
CallMe 
CallMeAgain 
TRUE 
+1

Это сделало мой день! Принимая ваш ответ. Спасибо, что нашли время, чтобы объяснить все подробности о VMT Delphi/C++ – BigONotation

Смежные вопросы