Forum: PC-Programmierung C++Builder: Ereignisbehandlung zur Laufzeit zuweisen


von Rainer R. (Firma: Reusch Elektronik) (reusch)


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!

von Jim M. (turboj)


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.

von Oliver R. (superberti)


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

von Rainer R. (Firma: Reusch Elektronik) (reusch)


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)

von Oliver R. (superberti)


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

von Rainer R. (Firma: Reusch Elektronik) (reusch)


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!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.