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

时间:2013-11-20 15:02:05

标签: c++ delphi callback

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

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

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

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

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

非常感谢。

3 个答案:

答案 0 :(得分:3)

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

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

  2. 您的回调函数未声明任何调用约定,因此正在使用Delphi的默认register调用约定。在C ++ Builder中,它对应于特定于供应商的__fastcall调用约定(不要与Visual C ++的__fastcall调用约定混淆,后者完全不同,并实现为{{1}在C ++ Builder中。

  3. 如果您只关心支持C ++ Builder,那么您可以保留DLL代码,相应的C ++代码如下所示:

    __msfastcall

    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)
    {
        //...
    }
    

    但是,如果需要支持其他C ++编译器,则需要更改DLL以支持标准C / C ++,例如:

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

    然后您可以在C ++中执行以下操作:

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

    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);
    }
    

答案 1 :(得分:2)

由于某些原因,你不能在DLL和C ++中拥有“对象”的功能。

  1. C ++类基础结构可能与Delphi不同,直到你会证明它们是相同的。应该证明“对象”是相同的共同点。 http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
  2. DLL没有类型安全性。它们只有“name = pointer”列表。因此,即使不同版本的Delphi也会有不同的,不兼容的类实现。
  3. 即使您将应用程序和DLL放在相同的Delphi版本中,您仍然会有两个不同的TObject类:EXE.TObjectDLL.TObject。虽然他们的实现有望成为彼此的克隆,但作为指针,它们会有所不同,因此任何像EXE.TForm is DLL.TComponent或类似DLL.TButton as EXE.TPersistent的类型检查都会失败,从而破坏了代码的逻辑,这会错误地预期OOP继承基础工作。
  4. 那么你能做些什么呢?你可以建立什么样的“共同点”?

    高科技选项是使用一些具有对象概念的丰富的跨平台ABI(二进制接口)。对于Windows,您通常使用Excel,Word,Internet Explorer等COM对象。所以你用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);
    }
    

    同样,你只能将这些数据类型用于参数,这是语言之间“最大的共同点”,如整数,双精度和指针。

答案 2 :(得分: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.

请注意,大部分代码都是自动生成的 只有标有////////的成员才不会。