2013-11-20 38 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更安全。 –

相關問題