0

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.

3

3 Answers 3

3

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);
Sign up to request clarification or add additional context in comments.

3 Comments

thanks for fixing my C++ draft. however i see you're using stdcall with underscores - are they required ?
"care about supporting C++Builder" - only exactly the same C++B/RTL/VCL build-version as one of Delphi. That is bery fragile! So i'd repeat it - if he only cares for EMBT tools, then he should write safe BPLs rather than DLLs
@Arioch: it depends on the particular C++ compiler whether leading underscores are needed on calling convention names, but in my experience, they usually are. And yes, if the OP only requires support for Borland/CodeGear/Embarcadero tools than a BPL would be safer than a DLL.
2

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.

Comments

1

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.

4 Comments

Automation is heavy. No real need for that.
@Johan I told COM as a #1 option too. The problem is that you don't know how to build an actual COM server in the unknown generic "C++". You uggest the wizard - but it would only work in a C++ Builder, and there you can just use native BPLs instead. And that wizard would not help to make a COM server in Watcom C++ or CLang or whatever
@DavidHeffernan he already has some object to receive events. "Of object". So i don;t think that creating one more proxy object would actually be very complex. He does not need Automation aka IDispatch - he need a good old statically-bound COM, ad hoc light-weight proxy
@Arioch'The yes, automation is too heavy, that's what I said

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.