如何在c中使用Delphi的回调函数
how to use callback function of Delphi is in c
我有一个用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++编译器都不支持:
-
回调使用
of object
修饰符,这意味着可以为回调分配对象实例的非静态方法。这是在C++Builder中使用特定于供应商的__closure
编译器扩展实现的。尽管标准C++确实有使用函数指针到对象方法的语法,但实现方式与__closure
的实现方式非常不同。 -
回调没有声明任何调用约定,因此使用了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"。
- C++类的基础设施可能与Delphi的基础设施不同,直到您能够逐点证明它们是相同的。"对象"应该被证明是相同的共同点。http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
- DLL没有类型安全性。它们只有"名称=指针"列表。因此,即使是不同版本的Delphi也会有不同的、不兼容的类实现
- 即使您在同一个Delphi版本中制作应用程序和DLL,您仍然会有两个不同的TObject类:
EXE.TObject
和DLL.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控件一样,如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的说法,但我认为当我们有在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.
请注意,这些代码大多是自动生成的
只有标有////////
的成员没有。
- 架构决策:返回std::future还是提供回调
- 正在为Xtensa simcall函数编写回调函数
- 如何在C++中使用非静态成员函数作为回调函数
- FLTK:按下哪个按钮 - 将数字传递给按钮的回调 (lambda)
- 在简单示例中,Python3 + ctypes 回调会导致内存泄漏
- 用于在回调中调用解析器的设计模式
- 如何使用C++对象的成员函数作为 C 样式回调?
- Java从C++回调到C++回调
- 如何将成员函数作为回调参数传递给需要"typedef-ed"自由函数指针的函数?
- 从不同的 cpp 调用回调函数会导致bad_function_call
- pcap_handler回调仅在使用 NPCAP v0.9991 时包含空数据包
- 不带轮询的 SDL2 事件回调
- 如何将C/C 回调函数转换为Delphi
- 将C 回调功能转换为Delphi
- Delphi 如何在另一个函数中使用回调函数作为参数
- 如何在c中使用Delphi的回调函数
- 使用 GetProcAddress: 回调函数从C++调用 Delphi DLL 失败,参数无效
- 指向Delphi中C++typedef回调过程的指针
- 在Delphi应用程序中使用c++ DLL回调函数
- 用c++从Delphi DLL中使用回调