user1844934
user1844934

Reputation: 1

how to use callback function of Delphi is in c

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

Answers (3)

Arioch 'The
Arioch 'The

Reputation: 16065

You cannot have functions "of object" in DLL and in C++ for few reasons.

  1. C++ classes infrastructure would probably be different from Delphi one, until you would prove they are identical bit to bit. "TObject" should be proved to be identical common ground. http://docwiki.embarcadero.com/RADStudio/XE5/en/Libraries_and_Packages_Index
  2. DLLs have no type safety. They only have "name=pointer" lists. So even different versions of Delphi would have different, incompatible implementations of classes.
  3. Even if you would make application and DLL in the same Delphi version, you would still have two different TObject classes: 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

Remy Lebeau
Remy Lebeau

Reputation: 598279

Your DLL is using two distinct features of Delphi that only C++Builder supports, no other C++ compiler does:

  1. 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.

  2. 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

Johan
Johan

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

Related Questions