Reputation: 1
I have a dll written in delphi which exports a function which is as follows
function LaneController_Init(OnIOChangeEvent:TOnIOChangeEvent):Integer; stdcall;
OnIOChangeEvent is a callback function and propertype is
TOnIOChangeEvent = procedure(sender:TObject;DeviceID,iFlag:Integer) of object;
Now my question is in C++,how to define callback function TOnIOChangeEvent?
Thanks a lot.
Upvotes: 0
Views: 2782
Reputation: 16065
You cannot have functions "of object" in DLL and in C++ for few reasons.
EXE.TObject
and DLL.TObject
. While their implementations would hopefully be clones of each other, as pointers they would be different and hence any checks like EXE.TForm is DLL.TComponent
or typecast like DLL.TButton as EXE.TPersistent
would fail, ruining the logic of your code, that would falsely expect OOP inheritance basics to work.So what can you do about that ? What kind of "common ground" you can build ?
Hi-tech option is to use some rich cross-platform ABI (binary interface) that have notions of objects. For Windows you typically use COM-objects like Excel, Word, Internet Explorer. So you make a COM Server in C++, that has some GUID-tagged interface that has the callback functions in it. Then you pass the interface-pointer to the callback function. Just like you use other COM-servers and Active-X controls like TExcelApplication
, TWebBrowser
and whatever. There also are other methods like CORBA, JSON-RPC, SOAP and others. And you would only be allowed to use parameters of data types standardized by those cross-platform interop standards.
For low-tech option you have to look at Windows API as a typical example of "flattening" object-oriented interface to non-object language.
function LaneController_Init(OnIOChangeEvent:TOnIOChangeEvent; ASelf: pointer):Integer; stdcall;
TOnIOChangeEvent = procedure(const Self: pointer; const Sender: Pointer; const DeviceID, iFlag:Integer); stdcall;
In Delphi, you would write something like
procedure OnIOChangeCallBack(const ASelf: pointer; const Sender: Pointer; const DeviceID, iFlag:Integer); stdcall;
begin
(TObject(ASelf) as TMyClass).OnIOChange(Sender, DeviceID, iFlag);
end;
Probably that would look like this in C++
void stdcall OnIOChangeCallBack(const void * Self; const void * Sender: Pointer; const int DeviceID; const int iFlag);
{
((CMyClass*)Self)->OnIOChange(Sender, DeviceID, iFlag);
}
Again, you would only be able to use those datatypes for parameters, that are "greatest common denominator" between languages, like integers, doubles and pointers.
Upvotes: 2
Reputation: 598279
Your DLL is using two distinct features of Delphi that only C++Builder supports, no other C++ compiler does:
Your callback is using the of object
modifier, which means the callback can be assigned a non-static method of an object instance. That is implemented in C++Builder using a vendor-specific __closure
compiler extension. Although standard C++ does have a syntax for using function pointers to object methods, the implementation is very different than how __closure
is implemented.
Your callback does not declare any calling convention, so Delphi's default register
calling convention is being used. In C++Builder, that corresponds to the vendor-specific __fastcall
calling convention (which is not to be confused with Visual C++'s __fastcall
calling convention, which is completely different, and implemented as __msfastcall
in C++Builder).
If you only care about supporting C++Builder, then you can leave the DLL code as-is and the corresponding C++ code would look like this:
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));
However, if you need to support other C++ compilers, then you need to change the DLL to support standard C/C++, for example:
type
TOnIOChangeEvent = procedure(DeviceID, iFlag: Integer; UserData: Pointer); stdcall;
function LaneController_Init(OnIOChangeEvent: TOnIOChangeEvent; UserData: Pointer): Integer; stdcall;
Then you can do the following in 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);
Upvotes: 3
Reputation: 76753
I agree with much that @Arioch is saying, but I think it's rather silly to use naked pointers
when we have interfaces, that work across the board on Windows.
I suggest you use a COM automation interface.
First create a standard program.
Inside that create a Automation Object using the steps outlined below.
See here for a tutorial, note that both Delphi and C++ builder code/examples are provided.
http://docwiki.embarcadero.com/RADStudio/XE3/en/Creating_Simple_COM_Servers_-_Overview
Once you've created your automation object, add an interface and at least one procedure to that interface.
You cannot reference Delphi objects in your code, it just will not port; however you can use your own interfaces, as long as you declare them in the type library editor
.
Make sure you set the parent interface to IDispatch
.
Now everywhere where you wanted to use an object, just use an appropriate interface.
Note that TObject
does not implement any interfaces, but TComponent
does. Many other Delphi objects implement interfaces as well.
You'll probably want to extend an existing object so as to implement your own interfaces.
Here's some sample code:
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.
Note that most of this code is auto-generated.
Only the members marked with ////////
are not.
Upvotes: 1