Как использовать функцию обратного вызова Delphi в C

0

У меня есть dll, написанный в delphi, который экспортирует функцию, которая выглядит следующим образом

function LaneController_Init(OnIOChangeEvent:TOnIOChangeEvent):Integer; stdcall;

OnIOChangeEvent - это функция обратного вызова, а свойство

TOnIOChangeEvent = procedure(sender:TObject;DeviceID,iFlag:Integer) of object;

Теперь мой вопрос в C++, как определить функцию обратного вызова TOnIOChangeEvent?

Большое спасибо.

Показать ещё 1 комментарий
Теги:
callback

3 ответа

2

Ваша DLL использует две различные функции Delphi, которые поддерживает только C++ Builder, ни один другой компилятор C++ не делает:

  1. В вашем обратном вызове используется модификатор of object, что означает, что обратному вызову может быть назначен нестатический метод экземпляра объекта. Это реализовано в C++ Builder с использованием расширения компилятора __closure конкретного __closure. Хотя стандартный C++ имеет синтаксис для использования указателей функций для методов объектов, реализация сильно отличается от того, как __closure.

  2. Ваш обратный вызов не объявляет ни соглашение о вызовах, поэтому Delphi по умолчанию register используется соглашение о вызовах. В C++ Builder это соответствует определенному вендором __fastcall вызове __fastcall (которое не следует путать с условным вызовом Visual C++ __fastcall, которое совершенно иное, и реализовано как __msfastcall в C++ Builder).

Если вы заботитесь только о поддержке C++ Builder, вы можете оставить код DLL как есть, а соответствующий код C++ будет выглядеть так:

typedef void __fastcall (__closure *TOnIOChangeEvent)(TObject *Sender, int DeviceID, int iFlag);
int __stdcall LaneController_Init(TOnIOChangeEvent OnIOChangeEvent);

void __fastcall TSomeClass::SomeMethod(TObject *Sender, int DeviceID, int iFlag)
{
    //...
}

TSomeClass *SomeObject = ...;
LaneController_Init(&(SomeObject->SomeMethod));

Однако, если вам нужно поддерживать другие компиляторы C++, вам необходимо изменить DLL для поддержки стандартного C/C++, например:

type
  TOnIOChangeEvent = procedure(DeviceID, iFlag: Integer; UserData: Pointer); stdcall;

function LaneController_Init(OnIOChangeEvent: TOnIOChangeEvent; UserData: Pointer): Integer; stdcall;

Затем вы можете сделать следующее в C++:

typedef void __stdcall (*TOnIOChangeEvent)(int DeviceID, int iFlag, void *UserData);
int __stdcall LaneController_Init(TOnIOChangeEvent OnIOChangeEvent, void *UserData);

void __fastcall TSomeClass::SomeMethod(int DeviceID, int iFlag)
{
    //...
}

// note: not a member of any class. If you want to use a class
// method, it will have to be declared as 'static'...
void __stdcall LaneControllerCallback(int DeviceID, int iFlag, void *UserData)
{
    ((TSomeClass*)UserData)->SomeMethod(DeviceID, iFlag);
}

TSomeClass *SomeObject = ...;
LaneController_Init(&LaneControllerCallback, SomeObject);
  • 0
    спасибо за исправление моего проекта C ++. однако я вижу, что вы используете stdcall с подчеркиванием - они требуются?
  • 0
    «заботиться о поддержке C ++ Builder» - только та же версия сборки C ++ B / RTL / VCL, что и в Delphi. Это очень хрупкий! Так что я бы повторил - если он заботится только об инструментах EMBT, он должен писать безопасные BPL, а не DLL
Показать ещё 1 комментарий
2

Вы не можете иметь функции "объекта" в DLL и в C++ по нескольким причинам.

  1. C++ инфраструктура классов, вероятно, будет отличаться от Delphi one, пока вы не докажете, что они идентичны бит-бит. "TObject" должен быть доказан одинаковой общей точкой. http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
  2. DLL не имеют безопасности типа. У них есть только списки "name = pointer". Таким образом, даже разные версии Delphi будут иметь разные, несовместимые реализации классов.
  3. Даже если вы EXE.TObject приложение и DLL в той же версии Delphi, у вас все равно будет два разных класса TObject: EXE.TObject и DLL.TObject. Хотя их реализации, мы надеемся, будут клонами друг друга, в качестве указателей они будут разными, и поэтому любые проверки, такие как EXE.TForm is DLL.TComponent или DLL.TButton as EXE.TPersistent например DLL.TButton as EXE.TPersistent потерпит неудачу, разрушая логику вашего кода, что будет ложно ожидать, что основы наследования ООП будут работать.

Так что вы можете с этим поделать? Какую "общую основу" вы можете построить?

Привет-Tech вариант заключается в использовании некоторых богатых кросс-платформенных ABI (двоичный интерфейс), которые имеют понятия объектов. Для Windows вы обычно используете COM-объекты, такие как Excel, Word, Internet Explorer. Таким образом, вы создаете COM-сервер в C++, который имеет некоторый интерфейс с GUID-метками, в котором есть функции обратного вызова. Затем вы передаете интерфейс-указатель на функцию обратного вызова. Так же, как вы используете другие COM-серверы и элементы Active-X, такие как TExcelApplication, TWebBrowser и т.д. Существуют также другие методы, такие как CORBA, JSON-RPC, SOAP и другие. И вам будет разрешено использовать только параметры типов данных, стандартизованные этими межплатформенными стандартами взаимодействия.

Для низкотехнологичного варианта вам нужно взглянуть на Windows API как типичный пример "сглаживания" объектно-ориентированного интерфейса на не-объектный язык.

function LaneController_Init(OnIOChangeEvent:TOnIOChangeEvent; ASelf: pointer):Integer; stdcall;


TOnIOChangeEvent = procedure(const Self: pointer; const Sender: Pointer; const DeviceID, iFlag:Integer); stdcall;

В Delphi вы напишете что-то вроде

procedure OnIOChangeCallBack(const ASelf: pointer; const Sender: Pointer; const DeviceID, iFlag:Integer); stdcall;
begin
  (TObject(ASelf) as TMyClass).OnIOChange(Sender, DeviceID, iFlag);
end;

Вероятно, это будет выглядеть в C++

void stdcall OnIOChangeCallBack(const void * Self; const void * Sender: Pointer; const int DeviceID; const int iFlag);
{
   ((CMyClass*)Self)->OnIOChange(Sender, DeviceID, iFlag);
}

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

1

Я согласен с тем, что @Arioch говорит, но я думаю, что глупо использовать голые pointers когда у нас есть интерфейсы, которые работают через панель в Windows.

Я предлагаю вам использовать интерфейс автоматизации COM.

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

См. Здесь учебник, обратите внимание, что предоставляются как код Delphi, так и C++.
http://docwiki.embarcadero.com/RADStudio/XE3/en/Creating_Simple_COM_Servers_-_Overview

Создав объект автоматизации, добавьте интерфейс и по крайней мере одну процедуру для этого интерфейса.
Вы не можете ссылаться на объекты Delphi в своем коде, он просто не будет порт; однако вы можете использовать свои собственные интерфейсы, если вы объявляете их в type library editor.
Убедитесь, что родительский интерфейс установлен в IDispatch.

Теперь везде, где вы хотели использовать объект, просто используйте соответствующий интерфейс.
Обратите внимание, что TObject не реализует никаких интерфейсов, но TComponent делает. Многие другие объекты Delphi также реализуют интерфейсы.
Вероятно, вы захотите расширить существующий объект, чтобы реализовать свои собственные интерфейсы.

Вот пример кода:

unit Unit22;

{$WARN SYMBOL_PLATFORM OFF}

interface

uses
  ComObj, ActiveX, AxCtrls, Classes,
Project23_TLB, StdVcl;

type
  TLaneController = class(TAutoObject, IConnectionPointContainer, ILaneController)
  private
    { Private declarations }
    FConnectionPoints: TConnectionPoints;
    FConnectionPoint: TConnectionPoint;
    FEvents: ILaneControllerEvents;
    FCallBack: ICallBackInterface;      /////////
    { note: FEvents maintains a *single* event sink. For access to more
      than one event sink, use FConnectionPoint.SinkList, and iterate
      through the list of sinks. }
  public
    procedure Initialize; override;
  protected
    procedure LaneController_Init(const CallBack: ICallBackInterface); safecall;
    { Protected declarations }
    property ConnectionPoints: TConnectionPoints read FConnectionPoints
      implements IConnectionPointContainer;
    procedure EventSinkChanged(const EventSink: IUnknown); override;
    procedure WorkThatCallBack;  /////////
    { TODO: Change all instances of type [ITest234Events] to [ILaneControllerEvents].}
 {    Delphi was not able to update this file to reflect
   the change of the name of your event interface
   because of the presence of instance variables.
   The type library was updated but you must update
   this implementation file by hand. }
end;

implementation

uses ComServ;

procedure TLaneController.EventSinkChanged(const EventSink: IUnknown);
begin
  FEvents := EventSink as ILaneControllerEvents;
end;

procedure TLaneController.Initialize;
begin
  inherited Initialize;
  FConnectionPoints := TConnectionPoints.Create(Self);
  if AutoFactory.EventTypeInfo <> nil then
    FConnectionPoint := FConnectionPoints.CreateConnectionPoint(
      AutoFactory.EventIID, ckSingle, EventConnect)
  else FConnectionPoint := nil;
end;


procedure TLaneController.LaneController_Init(const CallBack: ICallBackInterface);
begin
  FCallBack:= CallBack;
end;

procedure TLaneController.WorkThatCallBack;
const
  SampleDeviceID = 1;
  SampleFlag = 1;
begin
  try
    if Assigned(FCallBack) then FCallBack.OnIOChangeEvent(Self, SampleDeviceID, SampleFlag);
  except {do nothing}
  end; {try}
end;

initialization
  TAutoObjectFactory.Create(ComServer, TLaneController, Class_LaneController,
    ciMultiInstance, tmApartment);
end.

Обратите внимание, что большая часть этого кода генерируется автоматически.
Только члены, отмеченные ////////, не являются.

  • 0
    Автоматизация тяжелая. В этом нет особой необходимости.
  • 0
    @Johan Я тоже сказал COM как вариант №1. Проблема в том, что вы не знаете, как создать настоящий COM-сервер в неизвестном универсальном «C ++». Вы предлагаете мастер - но он будет работать только в C ++ Builder, и вместо этого вы можете просто использовать нативные BPL. И этот мастер не поможет сделать COM-сервер в Watcom C ++ или CLang или что-то еще
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню