PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Callback Funktion mit static function pointer



molleonair
09.03.2018, 18:05
Hallo zusammen ich habe ein Problem bei der Umsetzung einer callback Funktion..

Ziel soll sein das aus einer Klasse(TrackControl) heraus eine Funktion (sendmidi) im Haupt Sketch aufgerufen wird
dazu wollte ich einem static function pointer der Klasse die Referenz auf die Funktion übergeben.
Die einzelnen Instanzen der Klasse sollen dann die Funktion im Hauptsketch aufrufen können.
Ich habe das ganze schon umgeschrieben gehabt auf "extern void sendMidi (byte value);" und das hat funktioniert aber ich würde gern wissen
wie das mit dem callback richtig gemacht wird.

Code Hauptsketch:


#include "TrackControl.h"
//void sendMidi (byte); //prototyp needed ?
TrackControl trackcontrol[8];
void setup() {
TrackControl::setCallback(&sendMidi);
Serial.begin(9600);
}
void loop() {
trackcontrol[0].setVolume(1);
delay(1000);
}
void sendMidi (byte value){
Serial.print(value);
}


TrackControl.h:


#ifndef TrackControl_h
#define TrackControl_h
#include <arduino.h>

class TrackControl {
static void (*sendmidi)(byte); //pointer to function
public:
void setVolume (byte value);
static void setCallback (void (*pCallbackFunction)(byte)); //static method to setup callback function for all instances
};

#endif

TrackControl.cpp


#include "TrackControl.h"
#include <arduino.h>

void TrackControl::setCallback (void (*pCallbackFunction)(byte)){
sendmidi=pCallbackFunction;
}

void TrackControl::setVolume (byte value){
sendmidi(value);
}

Danke für eure Hilfe ..

shedepe
09.03.2018, 22:59
Um mal kurz in blaue zu Fragen: Was geht denn nicht? Macht das beantworten einfacher, dann muss man eventuell den Code nicht mal anschauen ;)

Prinzipiell hast du schon alles richtig gemacht. Du brauchst eine Statische Instanz des Callbacks. Da fehlt in der Cpp Datei noch

void (*TrackControl::sendmidi)(byte) = 0

eben weil es ja eine statische Variable sein soll.
Weil die Syntax bisschen nervig wird auf Dauer verwendet man für Functionpointers gerne typedefs.
Also z.B. in der Klasse drin:



typedef void (*sendmidi_fptr)(byte);
static sendmidi_ftpr callback;

und im cpp:
TrackControl::sendmidi_ftpr TrackControl::callback = 0;


Ansonsten ist die idee wie gesagt gut so:
Man hat eine setCallback Funktion und ruft den wie du es gemacht hast eben auf.
Ich würde vom Design her gerade bei Funktionspointern statische instanzen davon vermeiden, sondern lieber jeder Instanz den Pointer einzeln mitgeben. Das hat weniger Risiko für Nebenwirkungen wenn der Code mal komplexer wird.

Edit was ich noch vergessen habe: Das funktioniert so nur für Funktionen die außerhalb einer Klasse sind bzw. statisch in einer Klasse sind. Willst du einen Funktionspointer auf eine Memberfunction übergeben kannst du das nicht mit einem klassischen Funktionspointer so ohne Probleme machen. Mit C++ am PC geht das am besten mit std::function. Es es das auch für arduino gibt weiß ich nicht.

molleonair
10.03.2018, 12:34
Hallo shepede ..
Danke für deine Antwort .. als VB.Net Hobbyist kann ich das nur nicht umsetzten
meiner Meinung nach habe ich einen Funktionspointer in der Header Datei deklariert
und mit deiner "void (*TrackControl::sendmidi)(byte) = 0" initialisiert
die cpp sieht jetzt so aus:

#include "TrackControl.h"
#include <arduino.h>

void TrackControl::setCallback (void (*pCallbackFunction)(byte)){
void (*TrackControl::sendmidi)(byte) = 0;
TrackControl::sendmidi=pCallbackFunction;
}

void TrackControl::setVolume (byte value){
sendmidi(value);
}

die Fehlermeldung lautet:
sketch/TrackControl.cpp:6: undefined reference to `TrackControl::sendmidi'

die Lösung mit der statischen Klassen Funktion habe ich gewählt da ich sonst in jeder Instanz noch einen Pointer bräuchte und schon bei 70Prozent dynamischer Speicherauslastung bin
und dann noch für 128 Instanzen Pointer bräuchte.

shedepe
10.03.2018, 12:45
Lies dir noch mal durch wie man in C++ Statische Variablen verwendet. https://de.wikibooks.org/wiki/C%2B%2B-Programmierung:_Statische_Elemente_in_Klassen
Nur im Headerfile anlegen reicht nicht, weil das dem Compiler nur sagt, es soll so eine Variable geben. Erst durch anlegen im C++ File wird diese auch wirklich erst im Speicher erstellt.

molleonair
10.03.2018, 12:53
und was ist mit der
void (*TrackControl::sendmidi)(byte) = 0;

in der cpp ist das keine Intitialisierung ?

shedepe
10.03.2018, 14:58
Schau dir mal an was du geposted hast. Da steht das in einer Funktion drin.
void TrackControl::setCallback (void (*pCallbackFunction)(byte)){
void (*TrackControl::sendmidi)(byte) = 0;

molleonair
11.03.2018, 12:41
Sorry das ich dir da nicht folgen kann...

du hast geschrieben das diese Zeile in der cpp fehlt ..die einzige cpp ist TrackControl.cpp bestehend aus 2 Funktionen .. also muss ich deine Zeile zwangsweise ja in eine Funktion legen.
Danach hab ichs versuchsweise mal in der setup im .ino file probiert (vieleicht hast du ja die gemeint ) und habe trotzdem noch dieselbe Fehlermeldung.

(dein Code snippet aus Post #2 kompiliert auch nicht fehlerfrei.)

Ich hoffe hier findet sich noch jemand im Forum der in der Lage ist die 2 Zeilen Code die ich falsch habe zu berichtigen so daß ich das Konzept verstehen kann.
Ich suche jetzt schon mehrere Tage im Internet und finde keine Lösung für eine statische callback Methode.

An alle noch eine schönes Wochenende

shedepe
11.03.2018, 15:34
Dein Problem ist: Du willst eine statische Variable anlegen. Eine statische Variable im Headerfile anzulegen reicht nicht aus, weil aus einem Headerfile heraus der Compiler keine Speicherallokation durchführen kann. Deshalb musst du für eine statische Variable im allgemeinen Scope des C++ Files (Also nicht in einer funktion drin -> Da solltest du dir auch wirklich noch mal den Link den ich dir geposted habe durchlesen, ich poste das ja nicht umsonst).

D.h. diese Zeile solltest du außerhalb des Funktionsscopes haben:
void (*TrackControl::sendmidi)(byte) = 0;

molleonair
14.03.2018, 23:26
Hallo shepede ...
wie schon gesagt ich habe diese zeile in der setup im .ino file probiert die Fehlermeldung ist die gleiche.
Und in der CPP sind nur 2 Funktionen wo soll ich denn da außerhalb was hinschreiben.
kannst du mir nicht konkret sagen wo genau diese Zeile stehen muss damit es funktioniert ?
und den Link hab ich mehr als einmal gelesen.

shedepe
15.03.2018, 08:52
Dann solltest du vllt auch danach googlen was der globale Scope eines C++ Files ist.
Du gehst hin und schreibst direkt unter dem #include <dein headerfile> die Zeile für die Statische Variable hin. So steht das auch in dem Link und gehört leider so sehr zu den Grundlagen, dass ich dir noch mal ein gutes Tutorial anraten würde.

Also um es noch mal klar zustellen.



#include <TrackControl.h>

void (*TrackControl::sendmidi)(byte) = 0;

void TrackControl::setCallback (void (*pCallbackFunction)(byte)){
}

molleonair
15.03.2018, 17:32
Danke ....

Ich weiß das solche Probleme für andere ein Witz sind. Und das für mich das Konzept der Sprache C nicht gerade schlüssig ist merkt mann ja.
Ich entwickle hauptsächlich unter vb.net und in meiner Welt gibt es keinen Code Außerhalb von Klassen und Methoden. Auch Zugriff auf
Funktionen in anderen Klassen etc mach ich über den Namespace da brauch ich keinen callback für , es sei denn mal nen invoke.

Behaltet bitte im Hinterkopf daß hier nicht nur Profis hinterm PC sitzen . Schließlich ist ein Forum hauptsächlich da zu helfen und nicht
den Wissensstand eurer Mitmenschen zu kritisieren.

Trotzdem bin Ich froh das du mir die Lösung gezeigt hast ... da wär ich nie drauf gekommen... :-)

shedepe
15.03.2018, 17:53
Auch .Net kennt das Konzept von Callbacks. Nennt sich nur Delegates dort. Auf Funktionen aus anderen Klassen kannst du auch unter .Net nicht anders zugreifen als unter C++.

Ich wollte nur darauf hinweisen, dass man manchmal durch googlen auch weiterkommen kann. Gerade bei so Standardthemen. Dazu gibt es ja zig tausende Tutorials ;)
Auf der anderen Seite sind C Style Callbacks echt unschön. Unter C++11 am PC kann man das mit std::function viel schöner machen.

HaWe
15.03.2018, 18:39
Auch .Net kennt das Konzept von Callbacks. Nennt sich nur Delegates dort. Auf Funktionen aus anderen Klassen kannst du auch unter .Net nicht anders zugreifen als unter C++.

Ich wollte nur darauf hinweisen, dass man manchmal durch googlen auch weiterkommen kann. Gerade bei so Standardthemen. Dazu gibt es ja zig tausende Tutorials ;)
Auf der anderen Seite sind C Style Callbacks echt unschön. Unter C++11 am PC kann man das mit std::function viel schöner machen.

ist Arduino nicht ebenfalls C++(11) ?
warum geht das dann da nicht auch "viel schöner"?

shedepe
16.03.2018, 21:06
aja. Eine std::function und std::bind (was man braucht um eine Klassen funktion an eine std::function zu binden) sind meines Wissens nach etwas fett für die typischen Arduino Plattformen, da dort noch einiges mehr implementiert wird als eine reine Callback funktion.
Das ist zwar sehr praktisch da man Type Checks, checks auf Null usw. dazu bekommt, auf der anderen Seite braucht man wesentlich mehr Speicher (Was auf dem PC nicht stört), und der Funktionsaufruf dauert ein kleines bisschen länger.

Edit. Der vollständigkeit halber: https://blog.demofox.org/2015/02/25/avoiding-the-performance-hazzards-of-stdfunction/

HaWe
17.03.2018, 10:26
aja. Eine std::function und std::bind (was man braucht um eine Klassen funktion an eine std::function zu binden) sind meines Wissens nach etwas fett für die typischen Arduino Plattformen, da dort noch einiges mehr implementiert wird als eine reine Callback funktion.
Das ist zwar sehr praktisch da man Type Checks, checks auf Null usw. dazu bekommt, auf der anderen Seite braucht man wesentlich mehr Speicher (Was auf dem PC nicht stört), und der Funktionsaufruf dauert ein kleines bisschen länger.

Edit. Der vollständigkeit halber: https://blog.demofox.org/2015/02/25/avoiding-the-performance-hazzards-of-stdfunction/
ok, fetter Speicher verstehe ich, aber der Rest mit C++ std::function ist doch weit jenseits dessen was ich kapiere... ;)

shedepe
18.03.2018, 11:56
Ich versuch es dir spaßeshalber ein bisschen näher zu bringen.
Dazu muss man sich bewusst werden wie Funktionen bei C oder C++ im Speicher liegen. Zum einen gibt es Funktionen die außerhalb einer Klasse sind. Das sind ganz normale C Funktionen die irgendwo im Speicher liegen. Darauf einen Funktionspointer zu machen ist sehr einfach, weil man muss wirklich nur wissen: Wo liegt die Funktion. Da kommen wir aber schon zum ersten Problem. Normalerweise prüft der Compiler: Passen die Argumente die du angibst zu der Funktion. Bei Funktionspointer kannst du das umgehen wenn du willst.

Dazu auch noch eine nette Anmerkung: Man kann auch spaßeshalber hingehen und den Speicher in dem eine Funktion liegt durch eine andere Funktion zur Laufzeit überschreiben. Wenn man dann die ursprüngliche aufruft ruft man in wirklichkeit die überschriebene auf ;).

Aber nun der eigentliche Grund für std::function. Die zweite Art von Funktion die wir in C++ haben können sind Klassenfunktionen (Die man dann eigentlich Methoden nennt). Wenn wir jetzt eine Klasse A haben:



class A
{
public:
int a;
void do_something_on_a(){a++;}
};


Dann stellen wir fest es gibt zum einen Daten in einer Klasse, zum anderen Funktionen die auf den Daten arbeiten. Der Compiler geht hin und legt für jede Klasse einmal Speicher an in dem die ganzen Funktionen liegen. Die Daten werden jetzt aber für jede Instanz getrennt angelegt. D.h. jedes mal wenn wir A* neueInstanz = new A() machen. Wird Speicher für die Variable a reserviert. Wenn wir jetzt neueInstanz->do_something_on_a() aufrufen. Dann baut der Compiler eigentlich einen Aufruf zusammen in dem er die Funktion do_something_on_a() mit der entsprechenden Instanz aufruft.

Wenn du jetzt aber versuchst einen Funktionspointer auf do_something_on_a zu erstellen würde der ja in C-Style Syntax so aussehen: (Wenn ich nen Syntaxfehler drin hab kann sein. Bin was C-Style Funktionspointer anbelangt nicht so fit was die Syntax angeht)


(void) (*func)();

Das Problem dabei ist aber: Da ist nicht die Instanz mit eingebaut auf der die Methoden arbeiten soll. Man kann das zwar mit integrieren. Bringt aber weitere Probleme mit sich nämlich z.B. dass unterschiedliche Compiler funktionen unterschiedlich aufrufen. D.h. der Code ist plötzlich nicht mehr auf einer anderen Plattform lauffähig. (Und man kann die Typenprüfung umgehen wenn man will) (Das exakte kann man hier nachlesen: http://www.newty.de/fpt/fpt.html#defi)

Deshalb hat C++11 das std::function template mitgebracht. Damit kann man Plattformunabhängig Pointer auf Memberfunktionen erstellen und aufrufen und die Syntax ist viel netter.

HaWe
18.03.2018, 12:05
aaah, super, dankeschön, DAS habe ich kapiert! :idea: