【问题标题】:how to use callback function of Delphi is in c如何使用Delphi的回调函数在c中
【发布时间】:2013-11-20 15:02:05
【问题描述】:

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

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

OnIOChangeEvent是回调函数,propertype是

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

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

非常感谢。

【问题讨论】:

标签: c++ delphi callback


【解决方案1】:

您的 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);

【讨论】:

  • 感谢修复我的 C++ 草稿。但是我看到您正在使用带下划线的 stdcall - 它们是必需的吗?
  • “关心支持 C++Builder” - 仅与 Delphi 中的一个完全相同的 C++B/RTL/VCL 构建版本。那太脆弱了!所以我再重复一遍——如果他只关心 EMBT 工具,那么他应该编写安全的 BPL 而不是 DLL
  • @Arioch:调用约定名称时是否需要前导下划线取决于特定的 C++ 编译器,但根据我的经验,它们通常是。是的,如果 OP 只需要对 Borland/CodeGear/Embarcadero 工具的支持,那么 BPL 将比 DLL 更安全。
【解决方案2】:

您不能在 DLL 和 C++ 中使用“对象”函数,原因有几个。

  1. C++ 类基础结构可能与 Delphi 不同,直到您证明它们是相同的。 “TObject”应该被证明是相同的共同点。 http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
  2. DLL 没有类型安全性。他们只有“name=pointer”列表。所以即使是不同版本的 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);
}

同样,您只能将这些数据类型用于参数,它们是语言之间的“最大公分母”,如整数、双精度和指针。

【讨论】:

    【解决方案3】:

    我非常同意@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.
    

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

    【讨论】:

    • 自动化很重。没有真正的需要。
    • @Johan 我也告诉 COM 作为 #1 选项。问题是您不知道如何在未知的通用“C++”中构建一个实际的 COM 服务器。您建议使用该向导 - 但它只能在 C++ Builder 中工作,并且您可以只使用本机 BPL。而且那个向导不会帮助用 Watcom C++ 或 CLang 或其他任何东西制作 COM 服务器
    • @DavidHeffernan 他已经有一些对象可以接收事件。 “对象”。所以我不认为再创建一个代理对象实际上会非常复杂。他不需要自动化又名 IDispatch - 他需要一个好的旧静态绑定 COM,即席轻量级代理
    • @Arioch'是的,自动化太重了,我就是这么说的
    猜你喜欢
    • 1970-01-01
    • 2012-06-23
    • 2012-01-07
    • 1970-01-01
    • 1970-01-01
    • 2023-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多