2013-11-20 35 views
0

我在Delphi编写一个dll其中出口的函数,而如下如何使用Delphi的回调函数是在C

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

OnIOChangeEvent是一个回调函数和propertype是

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

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

非常感谢。

+0

里的东西这是什么?[[使用GetProcAddress:C++从C++调用Delphi DLL失败并返回无效参数](http://stackoverflow.com/a/9726231/576719)。 –

+1

你可以随意更改Delphi和C++代码吗? –

+1

您还需要决定使用哪种语言,C或C++ –

回答

2

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

  1. C++类的基础结构可能会与德尔福的基础结构不同,直到你证明它们是一致的。 “TObject”应该被证明是相同的共同点。 http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
  2. DLLs没有类型安全。他们只有“名称=指针”列表。所以即使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); 
} 

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

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作为第一选择。问题是你不知道如何在未知的通用“C++”中构建实际的COM服务器。您可以使用该向导 - 但它只能在C++ Builder中运行,您可以直接使用本地BPL。并且该向导无助于在Watcom C++或CLang中创建COM服务器或其他任何 –

+0

@DavidHeffernan,他已经有一些对象接收事件。 “对象”。所以我不认为创建一个更多的代理对象实际上会非常复杂。他不需要自动化又名IDispatch - 他需要一个很好的静态绑定的COM,特设的轻量级代理 –

2

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

然而,如果y OU需要支持其他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” - 与Delphi之一完全相同的C++ B/RTL/VCL构建版本。这是脆弱的!所以我会重复一遍 - 如果他只关心EMBT工具,那么他应该编写安全的BPL而不是DLL –

+0

@Arioch:它取决于特定的C++编译器是否需要在调用约定名称时使用前导下划线,但根据我的经验,他们通常是。是的,如果OP只需要支持Borland/CodeGear/Embarcadero工具,那么BPL将比DLL更安全。 –

相关问题