C++Builder: Ereignisbehandlung zur Laufzeit zuweisen

OP (Firma: Reusch Elektronik) #4570017
Lesenswert?
• ▲
▼
Hallo C++Builder-Fachleute!
In einem C++Builder-6-Projekt (ich weiß, der ist steinalt, die 
Fragestellung gilt aber auch für RAD-Studio XE) verwende ich eine 
nichtvisuelle Komponente (nennen wir sie "TSerial"). Sie wird zur 
Laufzeit erzeugt:

Serial = new TSerial(NULL);
Serial->Name = "ABC";

Die Komponente kennt das Ereignis "OnEventChar". Diesem Ereignis soll 
eine Funktion zugewiesen werden. Ist die Ereignisbehandlungsroutine 
innerhalb eines Formulars deklariert, etwa in der Art

void __fastcall TForm1::EventChar(TObject *Sender)
{
  ...
}

ist auch die Zuweisung kein Problem:

Serial->OnEventChar = EventChar;

***

In meinem Problemfall verwende ich die Komponente jedoch nicht in einem 
Formular sondern in einer DLL sozusagen "standalone", d.h. die Variable 
"Serial" ist eine globale Variable in der DLL. Da es kein Formular gibt, 
ist auch die Ereignisbehandlungsroutine als normale Funktion deklariert:

void __fastcall EventChar(TObject *Sender)
{
  ...
}

Dadurch funktioniert die Zuweisung einer Ereignisbehandlungsroutine, wie 
oben gezeigt, nicht mehr. Der Compiler wirft die Fehlermeldung

[C++ Fehler] Unit_Serial.cpp(109): E2034 Konvertierung von 'void 
(_fastcall *)(TObject *)' nach 'void (_fastcall * (_closure )(TObject 
*))(TObject *)' nicht möglich

aus. Der Typ der Ereignisbehandlungsroutine passt also nicht mehr ganz. 
Ich habe schon einiges rumprobiert und nach passenden Lösungen im Netz 
gesucht -- erfolglos. Vielleicht hat in diesem Forum jemand die richtige 
Lösung zur Hand, wie die Ereignisbehandlungsroutine in diesem Fall 
deklariert werden muss oder wie der Typecast bei der Zuweisung 
auszusehen hat.

Übrigens: Unter Pascal (Delphi) ist die Lösung sehr einfach. Der 
Funktionsdeklaration der Ereignisbehandlungsroutine wird einfach "of 
object" dran gehängt. Beim C++Builder wird das aber anders sein!

Für eure produktiven Lösungsvorschläge schon mal vielen Dank!
#4570036
Lesenswert?
• ▲
▼
Rainer R. schrieb:
> Übrigens: Unter Pascal (Delphi) ist die Lösung sehr einfach. Der
> Funktionsdeklaration der Ereignisbehandlungsroutine wird einfach "of
> object" dran gehängt. Beim C++Builder wird das aber anders sein!

Naja nicht wirklich. Eine Function "of object" besteht aus 2 Dingen: 
Einer Objektrefrenz und dem eigentlichen Methodenzeiger. Das kann man 
nicht auf einen einfachen Funktionszeiger casten.

Daher deine längliche Fehlermeldung, die Dir IMO aber auch eine mögliche 
Typdeklaration aufzeigt.
#4574054
Lesenswert?
• ▲
▼
Der C++-Builder benutzt für seine Events sogenannte Closures. Das sind 
Funktionspointer mit implizitem this-Pointer, um auch nicht-statische 
Objektmethoden aufrufen zu können.
Bei Microsoft heißt das im Allgemeinen Delegate.
Du brauchst für die Zuweisung des Events nicht unbedingt ein Form, 
sondern vielmehr ein lebendes Objekt, welches z.B. folgende Methode 
implementiert:
1
void __fastcall MyObject::EventChar(TObject *Sender)
2
{
3
  ...
4
}
und dann später
1
...
2
MyObject meins;
3
Serial->OnEventChar = meins.EventChar;
Falls Du ansonsten keine Objekte hast, kannst Du ja aus Deinem 
Hilfsobjekt "MyObject" die entsprechenden C-Funktionen oder was auch 
immer aufrufen.


Man kann auch eigene Closures deklarieren, wie z.B.
1
/// Event, wenn Aktionen für markierte Messungen ausgelöst werden
2
/// \param BrowserAction Gibt an, was mit der Selektion geschehen soll
3
/// \param Selection Indizes der ausgewählte Messungen (set)
4
typedef bool (__closure *TDBBrowserEvent)(const BrowserEventAction BrowserAction, const SelectionVec & Selection);
Dies deklariert beispielsweise eine Funktion, die einen bool 
zurückliefert und einen Enum sowie einen Vektor als Parameter erwartet.
In Deinem Programm kann man dann den Closure wie eine Variable 
verwenden:
1
TDBBrowserEvent MyBrowserEvent;

Gruß,
Oliver
OP (Firma: Reusch Elektronik) #4575463
Lesenswert?
• ▲
▼
Hallo Oliver!
Vielen Dank für deine Ausführungen!
An ein "Hilfsobjekt" habe ich auch schon gedacht, als Notlösung, falls 
sich doch keine "saubere" Lösung auftut. Da es diese in Pascal (Delphi) 
ja gibt, sollte es diese auch unter C++ geben. Du hast das mit Deinem 
zweiten Ansatz ja auch angedeutet. Das spiegelt auch das wider, was 
meine Internet-Recherche ergeben hat.

Angepasst auf meinen Anwendungsfall schreibe ich also:
1
typedef void __fastcall (__closure *TMyEvent)(TObject *Sender);
2
TMyEvent MyEvent;
3
Serial->OnEventChar = MyEvent;
Bis dahin schluckt der Compiler auch alles. Doch wie deklariere ich nun 
die eigentliche Ereignisbehandlungsroutine?
(irgendwie stehe ich auf dem Schlauch)
#4575498
Lesenswert?
• ▲
▼
Hi Rainer,

die Definition des Handlers habe ich in meinem Post ja schon 
hingeschrieben:
1
void __fastcall MyObject::MyEvent(TObject *Sender)
2
{
3
  ...
4
}

Im Header wäre dann die Deklaration
1
class MyObject
2
{
3
  ...
4
  void __fastcall MyEvent(TObject *Sender);
5
  ...
6
}

Du kannst also Deinem Event jede Member-Funktion eines beliebigen 
Objekts zuweisen, sofern sie den Rückgabewert void hat und ein TObject 
als Parameter erwartet.
Nochmal: Ein Closure ist IMMER ein Zeiger auf eine Memberfunktion eines 
Objekts. Das ist keine Notlösung, sondern genau so gewollt. Dies ist der 
Mechanismus, mit dem die VCL als Oberflächenframework arbeitet.


Gruß,
Oliver
OP (Firma: Reusch Elektronik) #4575571
Lesenswert?
• ▲
▼
Hallo Oliver!
Nun ja, das ist die Lösung mit dem Hilfsobjekt! Aber gut, das ist jetzt 
zwar nicht so elegant, die Umsetzung ist aber auch nicht allzu 
aufwändig. Zusammengefasst sieht das in etwa so aus:
1
class TMyObject {
2
public:
3
  void __fastcall SerialEventChar(TObject *Sender);
4
  __fastcall TMyObject(void);
5
  __fastcall ~TMyObject(void);
6
};
7
TMyObject *MyObject;
8

9
__fastcall TMyObject::TMyObject(void)
10
{
11
  //
12
}
13

14
__fastcall TMyObject::~TMyObject(void)
15
{
16
  //
17
}
18

19
void __fastcall TMyObject::SerialEventChar(TObject *Sender)
20
{
21
  ...
22
}
23

24
MyObject = new TMyObject();
25
Serial->OnEventChar = MyObject->SerialEventChar;
Nochmals vielen Dank für Deine Hilfe!

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren