У меня есть dll, написанный в delphi, который экспортирует функцию, которая выглядит следующим образом
function LaneController_Init(OnIOChangeEvent:TOnIOChangeEvent):Integer; stdcall;
OnIOChangeEvent - это функция обратного вызова, а свойство
TOnIOChangeEvent = procedure(sender:TObject;DeviceID,iFlag:Integer) of object;
Теперь мой вопрос в C++, как определить функцию обратного вызова TOnIOChangeEvent?
Большое спасибо.
Ваша DLL использует две различные функции Delphi, которые поддерживает только C++ Builder, ни один другой компилятор C++ не делает:
В вашем обратном вызове используется модификатор of object
, что означает, что обратному вызову может быть назначен нестатический метод экземпляра объекта. Это реализовано в C++ Builder с использованием расширения компилятора __closure
конкретного __closure
. Хотя стандартный C++ имеет синтаксис для использования указателей функций для методов объектов, реализация сильно отличается от того, как __closure
.
Ваш обратный вызов не объявляет ни соглашение о вызовах, поэтому 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);
Вы не можете иметь функции "объекта" в DLL и в C++ по нескольким причинам.
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);
}
Опять же, вы могли бы использовать только эти типы данных для параметров, которые являются "наибольшим общим знаменателем" между языками, такими как целые числа, двойники и указатели.
Я согласен с тем, что @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.
Обратите внимание, что большая часть этого кода генерируется автоматически.
Только члены, отмеченные ////////
, не являются.
Calling Delphi DLL from C++ using GetProcAddress: callback function fails with invalid parameter
.