如何在c中使用Delphi的回调函数

how to use callback function of Delphi is in c

本文关键字:Delphi 回调 函数      更新时间:2023-10-16

我有一个用delphi编写的dll,它导出一个函数,如下所示

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

OnIOChangeEvent是一个回调函数,属性类型为

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

现在我的问题是在C++中,如何定义回调函数TOnIOChangeEvent

非常感谢。

您的DLL使用Delphi的两个不同功能,只有C++Builder支持,其他C++编译器都不支持:

  1. 回调使用of object修饰符,这意味着可以为回调分配对象实例的非静态方法。这是在C++Builder中使用特定于供应商的__closure编译器扩展实现的。尽管标准C++确实有使用函数指针到对象方法的语法,但实现方式与__closure的实现方式非常不同。

  2. 回调没有声明任何调用约定,因此使用了Delphi默认的register调用约定。在C++Builder中,它对应于特定于供应商的__fastcall调用约定(不要将其与Visual C++的__fastcall调用约定混淆,后者完全不同,在C++Builder中将其实现为__msfastcall)。

如果您只关心支持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++中不能有函数"of object"。

  1. C++类的基础设施可能与Delphi的基础设施不同,直到您能够逐点证明它们是相同的。"对象"应该被证明是相同的共同点。http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
  2. DLL没有类型安全性。它们只有"名称=指针"列表。因此,即使是不同版本的Delphi也会有不同的、不兼容的类实现
  3. 即使您在同一个Delphi版本中制作应用程序和DLL,您仍然会有两个不同的TObject类:EXE.TObjectDLL.TObject。虽然它们的实现有望是彼此的克隆,但作为指针,它们将是不同的,因此任何像EXE.TForm is DLL.TComponent或像DLL.TButton as EXE.TPersistent这样的类型转换的检查都会失败,破坏代码的逻辑,错误地期望OOP继承基础能够工作

那么你能做些什么呢?你可以建立什么样的"共同点"?

高科技的选择是使用一些具有对象概念的丰富的跨平台ABI(二进制接口)。对于Windows,您通常使用COM对象,如Excel、Word、Internet Explorer。所以你用C++制作了一个COM服务器,它有一些GUID标记的接口,里面有回调函数。然后你把接口指针传递给回调函数。就像您使用其他COM服务器和Active-X控件一样,如TExcelApplicationTWebBrowser等等。还有其他方法,如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的说法,但我认为当我们有在Windows上全面工作的接口时,使用裸pointers是相当愚蠢的。

我建议您使用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.

请注意,这些代码大多是自动生成的
只有标有////////的成员没有。