[C++] Klassenmethoden an andere Funktionen übergeben (zB SetTimer)

Kampfgnom

Lt. Commander
Registriert
Jan. 2005
Beiträge
1.075
Hi
Nachdem ihr mir bisher immer so wunderbar geholfen habt kommen wir zur nächsten Frage.
Ich habe eine Klasse, die im Konstruktor eine eigene Methode als Timer initialisieren soll.
Sprich ich will an die SetTimer Funktion eine Funktion übergeben, die in einer meiner Klassen Member ist.

Hier mal die SetTimer-Funktion:
Code:
UINT_PTR SetTimer(      
    HWND hWnd,
    UINT_PTR nIDEvent,
    UINT uElapse,
    TIMERPROC lpTimerFunc
);
Und was TIMERPROC darstellen soll:
Code:
typedef void (*TIMERPROC)(HWND,UINT,UINT_PTR,DWORD)

Meine Methode deklarier ich so:
Code:
void CALLBACK MirandaPlugin::getValue(HWND hwnd,UINT ui,UINT_PTR uiptr,DWORD dw)
{
	MessageBox(NULL,"test","test",MB_OK);
}
oder auch
Code:
TIMERPROC MirandaPlugin::getValue(HWND hwnd,UINT ui,UINT_PTR uiptr,DWORD dw)
{
	MessageBox(NULL,"test","test",MB_OK);
}
das macht wenig Unterschied...

Nun hab ich ein wenig rumprobiert ( in MirandaPlugin::Init() ):

Code:
TIMER_ID=SetTimer(NULL,TIMER_ID,time*1000,(&MirandaPlugin::getValue));

führt zu
Code:
error C2664: 'SetTimer': Konvertierung des Parameters 4 von 'TIMERPROC (__thiscall MirandaPlugin::* )(HWND,UINT,UINT_PTR,DWORD)' in 'TIMERPROC' nicht möglich

Ganz gut hats schon geklappt mit:

Code:
TIMER_ID=SetTimer(NULL,TIMER_ID,time*1000,this->MirandaPlugin::getValue);
oder
Code:
TIMER_ID=SetTimer(NULL,TIMER_ID,time*1000,this->getValue);

das führt zu
Code:
error C2664: 'SetTimer': Konvertierung des Parameters 4 von 'TIMERPROC (HWND,UINT,UINT_PTR,DWORD)' in 'TIMERPROC' nicht möglich


Alle Möglichkeiten hab ich auch schon mit Cast auf (TIMERPROC) probiert. Da krieg ich dann immer
Code:
g:\C++\ogame\MirandaPlugin.cpp(43): error C2440: 'Typumwandlung': 'TIMERPROC (__thiscall MirandaPlugin::* )(HWND,UINT,UINT_PTR,DWORD)' kann nicht in 'TIMERPROC' konvertiert werden
oder
Code:
g:\C++\ogame\MirandaPlugin.cpp(43): error C2440: 'Typumwandlung': 'overloaded-function' kann nicht in 'TIMERPROC' konvertiert werden


Hat da vielleicht einer schonmal was mit gemacht?
Das selbe Problem hab ich auch wenn es um CreateThread(..) geht.

danke
mfg
 
Die Antwort ist ganz einfach :-) : So geht das nicht.

Warum nicht? Methoden (und so eine möchtest du übergeben) arbeiten auf Objekten. D.h. eine Methode kann alleine garnicht aufgerufen werden, sondern es gehört auch immer ein Objektzeiger (this !) dazu. Stell ihn dir als versteckten Parameter an die Methode vor.

Wenn du mit &Klasse::Methode einen Pointer-to-Member formst, brauchst du auch immer ein Objekt, mit dem du die Methode mit den Operatoren .* oder ->* aufrufst.

Das passt aber überhaupt nicht zu dem, was die Funktion erwartet, nämlich einen Zeiger auf eine Funktion.



Was kann man hier machen? Normalerweise bieten fast alle Callbacks der WinApi die Möglichkeit, beim Registrieren einen konstanten Parameter mitzugeben, mit dem die Callback-Funktion dann wieder aufgerufen wird. Bei CreateThread ist es ähnlich, da stopft man einen void-Pointer rein zusätzlich zum Funktionszeiger.
Anhand des Wertes, mit dem dein Callback aufgerufen wird, kannst du jetzt wieder das passende Objekt ermitteln, von dem du eine Methode aufrufen willst. Z.b. kannst du dir in einem Singleton eine Map von Identifizierern (die durch das Callback gereicht werden) und Objektzeigern halten.
Wenn du ein Beispiel dazu brauchst, sag es nur :)
 
Danke schonmal.
Dass es so nicht geht hab ich mir fast schon gedacht, so viele Möglichkeiten der Syntax blieben gar nicht mehr übrig :) .
Zu deinem Vorschlag: Ich habe die Richtung verstanden in die dein Tip geht, und eine dunkle Ahnung was du meinst, mehr aber auch nicht. Mit einem Beispiel wäre mir sehr geholfen, danke.

EDIT: Nach mehrmaligem durchlesen deiner Antwort, bin ich zu dem Schluss gekommen, dass du trotzdem noch auf eine globale Funktion zurückgreifen würdest. Genau das will ich ja verhindern. Ich habe nur eine Methode, die ich regelmäßig aufrufen will, ich brauche da keine Identizizierer oder Ähnliches.
Es ging mir wirklich nur ums Loswerden meiner letzten beiden globalen Funktionen.
Da ist doch definitiv ein Design-Fehler in C++, wenn es einem nicht möglich ist, gänzlich objektorientiert zu programmieren.

mfg
 
Zuletzt bearbeitet:
Puh... hab gerade mal das hier runtergetippelt:

Code:
#include <map>
#include <utility>

.....

class Identifiers
{
   public:
      static Identifiers& instance();

      typedef void (MirandaPlugin::* Meth)(HWND, UINT, UINT_PTR, DWORD);


      void registerIdentifier( UINT_PTR id, MirandaPlugin* obj, Meth meth);
      void removeIdentifier( UINT_PTR id);
      void callIdentifier( HWND wnd, UINT msg, UINT_PTR id, DWORD time);



   private:
      Identifiers() {}
      ~Identifiers() {}
      Identifiers( const Identifiers&);
      Identifiers& operator=( const Identifiers&);

      typedef std::pair<MirandaPlugin*, Meth> Call;
      typedef std::map<UINT_PTR, Call> Calls;
      Calls _calls;
};

Identifiers& Identifiers::instance()
{
   static Identifiers single;
   return single;
}

void Identifiers::registerIdentifier( UINT_PTR id, MirandaPlugin* obj, Meth meth)
{
   _calls[id] = std::make_pair( obj, meth);
}

void Identifiers::removeIdentifier( UINT_PTR id)
{
   _calls.erase( id);
}

void Identifiers::callIdentifier( HWND wnd, UINT msg, UINT_PTR id, DWORD time)
{
   Calls::iterator it = _calls.find( id);
   if( it != _calls.end()) {
      (it->second.first->*it->second.second)( wnd, msg, id, time);
   }
}

void CALLBACK timerProc( HWND wnd, UINT msg, UINT_PTR id, DWORD time)
{
   Identifiers::instance().callIdentifier( wnd, msg, id, time);
}


int main(int argc, char *argv[])
{
   MirandaPlugin plug;

   UINT_PTR id = SetTimer( xxx, xxx, xxx, &timerProc); // hier Parameter einsetzen
   Identifiers::instance().registerIdentifier( id, &plug, &MirandaPlugin::getValue);
   // später removeIdentifier nicht vergessen
}


Ich hoffe das veranschaulicht die Verwendung :) Sollte so funktionieren, es kompiliert auf jeden Fall *g*. Mit Identifiers::instance() kommst du an das Singleton und kannst die entsprechenden Methoden aufrufen. Registrieren tust du dann eben genau einen Identifizierer zusammen mit einem Objekt und einem Methodenzeiger.

Das ganze ist allerdings hardcoded auf deine Miranda-Plugin Klasse. Über ein Template hätte man hier noch die Klasse konfigurierbar machen können (das würde aber noch eine weitere Ebene der Indirektion erfordern - dazu war ich zu dieser Stunde aber zu faul *g*).
Viel mehr kann man an diesem Konzept leider nicht generisch machen. So wie ich es dir aufgeschrieben habe, lässt es sich nur mit SetTimer verwenden. Um hier Generik reinzubringen, müsste man variable Argumentlisten übergeben können (Die Signatur und die Semantik von Callbacks ist normalerweise recht verschieden und kaum/nicht generalisierbar). Sowas geht so mit C++ leider (?) nicht. Auch die Ellipse (...) hilft hier nicht.

Ich hoffe es ist aber leicht verständlich, wie du das auf ein anderes Callback anpassen kannst :) Bei Fragen fragen ;)

Gruß
 
Wow, da haste dir ja echt was zusammengebastelt... :) Ich blick da auch so weit durch, auch wenn ich bis vor 5 Minuten keine Ahnung von diesen Maps da hatte.;) Die Syntax kommt mir jetzt noch seltsam vor.

Aber jetzt mal im ernst: Mein Zeil war es diese blöde Globale Funktion loszuwerden, und das ist dir auch nicht gelungen. Bei meinem Programm wäre das der totale Overkill. Hier mal der zur zeitige aufbau:

  • int __declspec(dllexport)Load(PLUGINLINK *link):
  • plugin=new MirandaPlugin(); //plugin ist global
  • MirandaPlugin():
  • TIMER_ID=SetTimer(NULL,TIMER_ID,time*1000,timerProc); //timerProc ist global
  • timerProc():
  • plugin->hThreadID=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)startThread,NULL,0,(LPDWORD)&iThreadID);
  • startThread():
  • plugin->UnderAttack();
und dieses UnderAttack ist eigentlich der Mist den ich einfach nur ab und zu mal als Thread starten will.
Jetzt hab ich halt da diese beiden Globalen Funktionen und einen Public-Member hThreadID. Aber das ist immer noch besser als das was du da machst :D .

Naja ich bedanke mich für die Mühe, und werde versuchen aus deinem Code ein wenig zu lernen. Das kommt immer gut.

EDIT: Im überigen, du bist ja anscheinend hier sowas wie der C++ Crack, und kannst nebenbei auch noch alles andere... Wie alt bist du eigentlich (wenn man fragen darf ;)), als was arbeitest du, und woher hast du die Ahnung?
Ich mein man muss ja langsam auch mal Anfangen sich gedanken zu machen über etwaige spätere Jobs. :)

mfg
 
Zuletzt bearbeitet:
Naja die globale Funktion wird man nicht los. Geht nicht. Man braucht eine stinknormale Funktion, die "zurückgerufen" wird. Diese muss auch die richtige Aufrufkonvention haben (was man mit CALLBACK macht, was letztendlich stdcall ist). Aufrufkonvention ist das Format, wie die Daten im Speicher an Windows übergeben werden und zurück.

Naja, man muss halt immer überlegen, in welchem Ausmaß man so ein Callback realisiert, wenn man nur eine einfache C-Funktion hat. Wenn du einfach quasi immerwieder das selbe Objekt und die selbe Methode aufrufen willst, ist es wohl eher overkill :) Zum Lernen und Probieren ist sowas aber nie verkehrt (soviel zum Punkt C++ Wissen ;)).


Eine map ist übrigens ein sogenannter assoziativer Container, der Schlüssel zusammen mit Werten speichert. Der Zugriff auf die Werte über einen Schlüssel erfolgt in logarithmischer Zeit, was der eigentliche Vorteil von maps ist (in einer Liste von 65536 Einträgen bräuchte man nur 16 Suchvorgänge).


Naja zu mir, ich bin jetzt 23 und habe eine Ausbildung zum Fachinformatiker Anwendungsentwicklung gemacht. Davor ein Semester lang Informatik-Studium (Uni), hab aber gemerkt, dass das nicht so richtig mein Fall ist.
Durch entsprechende Projekte auf Arbeit lernt man ziemlich schnell viel Zeugs über C++. Momentan arbeite ich für meine Firma bei einem großen deutschen Mobilfunkkonzern an einem Projekt mit über 120 Modulen und 3000 Dateien :) Da braucht man viel Disziplin und Ordnung, um sich mit den anderen Leuten abzustimmen und vernünftig zusammenzuarbeiten. Aber es macht Spaß, und das ist das Wichtigste ;) Also wenn du Spaß am Programmieren hast, ist die Ausbildung vielleicht auch eine Überlegung für dich wert. Du müsstest dann halt aber eben drauf achten, was das für eine Firma ist. Bei einer Internet-Firma lernt man wohl eher wenig bis garnicht C++. Auf der anderen Seite habe ich bis jetzt quasi noch garkein Web-Zeugs gemacht (was mir auch recht so ist :)).

Ansonsten treib ich mich auch immer mal auf www.c-plusplus.de/forum rum. Allein vom Mitlesen wird man schon schlauer. Selber anderen helfen trainiert auch ungemein. Und bei Fragen natürlich auch nie zurückhalten. Und wenn du news liest (ich tu's nicht/selten *g*), comp.lang.c++ und comp.lang.c++.moderated sind auch immer mal einen Abstecher wert.
 
Zuletzt bearbeitet:
Jo, erstes Semester Informatik. Hab ich auch mal reingeschnuppert. DAP1 in Dortmund... Hat dir wohl zu wenig mit Programmieren zu tun gehabt *g*.

Naja, danke für alles und bis zur nächsten Frage.

mfg
 
Kampfgnom schrieb:
Hat dir wohl zu wenig mit Programmieren zu tun gehabt *g*.
Kann man so nicht sagen :D Im ersten Semester nimmt man das alles noch nicht so ernst. Und das bricht einem in Mathe das Genick :) In der Tat liegt mir aber auch das Praktische mehr. Immerhin, was ich in dem Semester mitgenommen habe, hat sich auch schon ein paar mal als recht nützlich erwiesen. Schlecht ist so ein Studium also auch auf jeden Fall nicht.

So far...
 
Kampfgnom schrieb:
...
EDIT: Nach mehrmaligem durchlesen deiner Antwort, bin ich zu dem Schluss gekommen, dass du trotzdem noch auf eine globale Funktion zurückgreifen würdest. Genau das will ich ja verhindern. Ich habe nur eine Methode, die ich regelmäßig aufrufen will, ich brauche da keine Identizizierer oder Ähnliches.
Es ging mir wirklich nur ums Loswerden meiner letzten beiden globalen Funktionen.
Da ist doch definitiv ein Design-Fehler in C++, wenn es einem nicht möglich ist, gänzlich objektorientiert zu programmieren.

mfg

Das ist kein design fehler in C++. das problem ist das callbacks nunmal so nicht objekt orientiert sind. für ein echtes oop plugin müsstest du also auf miranda verzichten, das is nunmal nur in c ;).

für objekt orientierte callbacks kannst du ansonsten mal nach signals und slots googlen. das wird dir zwar hier nix helfen weil miranda ja nunmal mit normalen callbacks arbeitet, aber wenn du selber mal eine eigenständige guis schreiben willst ist das extrem nützlich.

ansonsten vieleicht mal ein buch über c++ design patterns zulegen, vieleicht findet sich da sogar noch was besseres für dein callback problem. selbst wenn nicht, so ein buch ist für absolut jedes projekt eine riesen hilfe. man muss ja nicht immer das rad selbst neu erfinden.


wenn dich die globale funktion wirklich so sehr stört könnte man zumindest eine static methode draus machen. die lässt sich ganz einfach wie eine globale funktion nutzen(über klassenName::meineStaticFunction), gehört aber zu einer klasse. is zwar auch eher nur eine kosmetische verbesserung, aber das gehört zum klassen design eben auch dazu ;).
 
Zuletzt bearbeitet:
Ein Buch in die Richtung hab ich mir schonmal zugelegt. Da ging es mehr um Optimierung von Spielen, aber das Thema Patterns oder so wurde auch abgehandelt. Leider habe ich das "damals" (vor knapp 2 Jahren) irgendwie absolut nicht verstanden und dann irgendwohin ausgeliehen.
Ich glaub ich hol mir das mal zurück, inzwischen sollte ich das wohl ein wenig besser verstehen. :)

Und mit den globalen Funktionen hab ich mich inzwischen abgefunden, zumal ich sie inzwischen nur noch in einer einzigen CPP habe, sie also in der main.cpp gar nicht mehr sichtbar sind. Also gar nicht mehr SO schlimm...
naja, ich hab viel aus dem Projekt gelernt.

mfg
 
Japps, das Buch kann ich auch nur empfehlen, das ist quasi ein Meilenstein, an dem man auf jeden Fall vorbei gekommen sein sollte. Das Buch ist in der ersten Auflage übrigens von 1996 ;)
 

Ähnliche Themen

Zurück
Oben