PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Probleme mit Callback Funktion...


Matrix316
2003-10-16, 18:37:36
Also ich soll ein kleines Programm schreiben, was ein kleines Fenster öffnet und wenn ich eine Taste drücke soll der name des Zeichens zu sehen sein. Nur gibt es dauernd Fehlermeldungen beim starten (access Violation...) und der Buchstabe ändert sich nicht. Außerdem funktioniert z.B. "initkeypressed(...)" nicht, weil irgendwas nicht ... ist oder so.

Hier mal der Code:

windos.h

void initrepaint (void (*callback) ());
void initkeypressed (void (*callback) (char));
void wdprintf (char *text);


appl.cpp

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include "windos.h"

/*globale Variablen*/
char *wdkp;
char help='0';
int oben=0, unten=0, rechts=0, links=0;

/* Callback für Keypressed */
void keypressed (char key)
{
help=key;
wdkp=&help;
}

/* Callback für repaint */
void repaint()
{
system ("cls");
wdprintf(wdkp);
}

int wdmain()
{
//initkeypressed(keypressed);
//initrepaint(repaint);
return 0;
}


windos.cpp

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <ios.h>

/*globale Variablen*/

char ch;
void (*callb)(char);
void (*callb2)();

/*Funktionsprototyp*/
int wdmain ();


/*Ausgabefunktion für Applikation*/
void wdprintf(char *keey)
{
printf("*------------------*\n");
printf("* *\n");
printf("* you did press: *\n");
printf("* %c *\n",*keey);
printf("* to end press esc *\n");
printf("* *\n");
printf("*------------------*\n");
}

/*Callback für Keypressed*/
int initkeypressed(void(*callback)(char))
{
callb=callback;
return 0;
}

/*Callback für Repaint*/
int initrepaint(void(*callback)())
{
callb2=callback;
return 0;
}

/*Daten als Event an die entsprechende Applikation weiterleiten*/
void wdkeypressed(char ch)
{
(*callb)(ch);
}


/*Meldung an Applikation zum Neuzeichnen*/
void wdrepaint()
{
(callb2)();
}


int main()
{
wdmain();
while((ch=getch()) != 27){
wdkeypressed(ch);
wdrepaint();
}
return 0;
}


Die I/O der Application soll hier von "windos" übernommen werden.

Ich hoffe es kann jemand :help:

Matrix316
2003-10-16, 20:56:37
Hieeeeeeeeeeeeeelfeeeeeeeeeeeee :help:

Matrix316
2003-10-16, 21:32:46
Kann dann vielleicht jemand genau erklären wie callback über mehrere Files genau funktioniert (und das möglichst ohne das Wort Callback zu benutzen ;))...

Ich hab hier z.B. einen Beispielcode für Callback, der auch relativ verständlich ist, nur werden alle funktionen in nur einem File verwendet. Hier sind es jedoch insgesamt drei, was irgendwie ziemlich kompliziert ist.

Beispiel
#include <stdio.h>
#include <conio.h>
/* Callback-Registrierung: */
int init (int (*callback)())
{
return (*callback) ();
}

/* Callback: */
int myfunction ()
{
printf ("In myfunction! \n");
getchar();
return 0;
}

int main ()
{
init (myfunction);
return 0 ;
}

Demirug
2003-10-16, 21:35:15
Arrrrrg Funktionszeiger. Sorry das musste sein die Dinger gehören einfach verboten.

wenn du in der wdmain die beiden Funktionen welche die Funktionszeiger setzten nicht aufrufts sind diese natürlich Mist und ein Aufrauf damit geht ins Leere -> access Violation

Der Grund das die sache nicht passt dürfte sein das der Rückgabetype nicht stimmt. Das ganze müsste so aussehen.

int initrepaint (void (*callback) ());
int initkeypressed (void (*callback) (char));
void wdprintf (char *text);

Wer zur Hölle lässt euch aber solchen Code schreiben? In Zeiten der OOP sollte man sowas keinen mehr zumuten.

Matrix316
2003-10-16, 21:45:41
Tja, die Vorlesung heißt: Systemschnittstellen...

aber...

JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

es funktioniert!!!!!!

:massa: :laola: :massa:

Komisch ist nur, dass in der Vorlage auch "void" steht... :kratz2:

Demirug
2003-10-16, 21:52:00
Original geschrieben von Matrix316
Tja, die Vorlesung heißt: Systemschnittstellen...

OK, in diesem Zusammenhang macht das ganze begrenzt Sinn. Wenn man seine Systenmschnitstelle für eine nicht OOP Sprache braucht sind Funktionszeiger das einzige was funktioniert.

Komisch ist nur, dass in der "Beispiellösung" auch "void" steht... :kratz2:

Es gibt C Compiler die sind da nicht so genau und lassen sowas durchgehen.

PS: Brauchts du jetzt eigentlich noch die Erklärung?

Matrix316
2003-10-16, 22:02:56
Hmmm, ich glaube besser wäre es. ;)

Wobei "Zeiger auf Funktion" erklärt schon ein wenig. Nur WIE jetzt genau die "Kommunikation" zwischen den Programmen funktioniert wäre interessant zu wissen. Also was jetzt z.B. (*callb)(ch) genau macht...

Demirug
2003-10-16, 22:26:32
OK, Ich hole mal etwas weiter aus und hoffe ich langweile dich jetzt nicht mit Zeug das du schon aus den Vorlesungen kennst.

Für Systemschnitstellen gibt es zwei grundsätzliche Modele.

1. Das Pollmodel
2. Das Ereignissmodel.

Beim ersten Model stellt das System funktionen zur verfügung um den aktuellen Zustand zu erfragen. Zum Beispiel ob eine Taste gedrückt wurde oder nicht. Interesiert sich eine Anwendung für Tastatureingaben muss sie ständig diesen Funktion aufrufen. Das nennt man dann Pollen. Das gute alte DOS arbeitet nach diesem Model.

Für ein Multithread/Prozess System ist das natürlich nicht sonderlich gut zu gebrauchen. Jeder Prozess würde zum Beispiel durch das ständige Abfragen von Tastatureingabe den Prozessor unnötig belasten. Ein Stromsparmodus wäre so auch nicht möglich. Deswegen geht man hier nun den umgekehrten Weg. Das System meldet sich bei der Applikation wenn etwas passiert was diese interesiert. Damit das Funktioniert braucht man zum Beispiel Callbackfunktionen. Eine solche Funktion soll immer dann ausgeführt werden wenn ein bestimmtes Ereigniss aufgetreten ist. Dazu muss man dem System aber erst einmal mitteilen das man diese Ereignisse nutzen möchte. Dafür gibt es dann entsprechenden Funktionen zum Anmelden einer Callbackfunktion. Diese Funktionen bekommen als Parameter die Addresse an welcher sich die Callbackfunktion im Speicher befindet übergeben und speichert sie. Das ganze ist das ein Funktionszeiger weil es sich um eine Addresse (Zeiger) handelt wo man den Code (die Funktion) finden kann. Erkennt nun später das System das auftreten des entsprechenden Ereignisses lädt es die Addresse wieder ein und führt die Programmausführung an der im Zeiger gespeicherten Stelle fort. Am Anfang hattest du den Zeiger aber nicht mit einem Wert versorgt welcher auf ausführbaren Code verwiesen hat. Deswegen gab es einen Absturz. Bei einem echten System sollte das natürlich nicht passieren. Das erreicht man dadurch das vor dem Aufruf gebrüft wird ob der Zeiger gültig ist oder man den Zeiger falls die Anwendung kein Interesse an dem Ereigniss hat auf eine Dummyfunktion die nichts tut setzt.

Das ganze ist ähnlich den virtuellen Calls bei der OOP. Funktionszeiger stellen einie möglichkeit da eine Funktion aufzurufen die man zum Zeitpunkt der Compilierung noch nicht kennt weil sie erst dynamsich zur Laufzeit festgelegt wird.

Matrix316
2003-10-16, 22:45:23
Ok. Das hab ich auch schon in der Vorlesung gehört. ;D

Aber WAS GENAU passiert z.B. in den folgenden funktionen:

int initkeypressed(void(*callback)(char))
{
callb=callback;
return 0;
}

void wdkeypressed(char ch)
{
(*callb)(ch);
}

Xmas
2003-10-17, 01:11:29
int initkeypressed(void(*callback)(char))
{
callb=callback;
return 0;
}
Diese Funktion nimmt einen Pointer auf eine Funktion mit der Signatur void xyz(char) als Parameter und speichert diesen Pointer in der globalen Variable callb.

void wdkeypressed(char ch)
{
(*callb)(ch);
}
Diese Funktion nimmt einen char als Parameter und ruft die Funktion, die zuvor initkeypressed in Form eines Pointers übergeben wurde, über den in callb gespeicherten Funktionspointer auf. Dabei wird der char ch an diese Funktion weitergereicht.

Crushinator
2003-10-17, 01:13:26
@Matrix

In der ersten Funktion wird ein Zeiger names callb mit dem Wert (also der Adresse) der Funktion callback gefüllt, und in der Zweiten wird die sich an callb befindlichen Prozedur - welche hoffentlich korrekt und rechtzeitig referenziert wurde - aufgerufen.

Einfach ausgedrückt: callb soll eine Prozedur werden, die aufgerufen werden soll. Damit auch wirklich eine Funktion ausgeführt wird, muß callb auf die Adresse einer solchen Funktion verweisen, und genau diese Zuweisung würde in der Funktion initkeypressed(...) passieren.

Das heißt: Irgedwann wird in einem Programm entschieden, daß bei einem "KeyPress" die Funktion *LangweileDichzuTode(char key) oder *MachWasSinnVolles(char key) ausgeführt werden soll. Ich entscheide mich nach Laune für Ersteres und sage dann

/* Entschideung, welche Funktion das nun sein soll */
initkeypressed(LangweileDichzuTode);
/*
Innrehalb dieser Funktion wird der globale Zeiger
callb auf die Adresse von LangweileDichzuTode(char key)
gesetzt.
(*callb)(ch); ist dann dasselbe wie LangweileDichzuTode(ch);
*/

Absofort würden all meine gedrückten Tasten eine Langeweile auslösen. Aaaaber, im Laufe des Programms fällt mir ein, daß mir der Zustand irgendwie doch nicht gefällt und entscheide mich um:

/* Och nö, Laß' mal die Produktivitätskurve verbessern */
initkeypressed(MachWasSinnVolles);
/*
Innrehalb dieser Funktion wird der globale Zeiger
callb auf die Adresse von MachWasSinnVolles(char key)
gesetzt.
(*callb)(ch); ist dann dasselbe wie MachWasSinnVolles(ch);
*/

Damit lösen die KeyPress-Aktionen ab sofort was Sinnvolles aus. :D

P.S.: Callback über mehrere Files funktioniert hier deshalb, weil "appl.cpp" die "windos.cpp" includet und damit sowohl Zugriff auf die globalen Variablen als auch auf die Elemente hat.

Matrix316
2003-10-17, 10:44:25
Aaaaha. Die Firma dankt. ;) Also Pointer sind alleine schon kompliziert, aber sowas sollte wirklich verboten werden. ;) Wozu gibts Klassen...=);)

Jetzt noch eine klitzekleine fast offtopic Frage: Was für eine Ascii Nummer haben die Pfeiltasten? In dem Programm soll nämlich noch der kleine Rahmen mit dem Text mit den Pfeiltasten/Cursortasten bewegt werden...

Crushinator
2003-10-17, 12:15:34
^^

KEY_CURSOR_UP 72
KEY_CURSOR_DOWN 80
KEY_CURSOR_RIGHT 77
KEY_CURSOR_LEFT 75
KEY_PAGE_UP 73
KEY_PAGE_DOWN 81
KEY_POS1 71
KEY_END 79


:)

Xmas
2003-10-17, 12:15:52
Cursortasten haben gar keine "ASCII-Nummer", denn ASCII ist ein Zeichensatz, kein Tastaturlayout. Sie haben allerdings einen entsprechenden Steuercode, ich glaube jedoch der ist bei getch() multibyte, sprich du müsstest mehrere Bytes nacheinander mit getch() abfragen. Das lässt sich in dein bestehendes System kaum integrieren.

edit:
Das oben bezog sich auf getc(). getch() ist Bestandteil von curses.h, was keine ANSI-C Funktionalität ist. In Curses kannst du die Steuercode-Übersetzung mittels keypad() aktivieren.

Matrix316
2003-10-17, 12:25:05
Original geschrieben von crushinator
^^

KEY_CURSOR_UP 72
KEY_CURSOR_DOWN 80
KEY_CURSOR_RIGHT 77
KEY_CURSOR_LEFT 75
KEY_PAGE_UP 73
KEY_PAGE_DOWN 81
KEY_POS1 71
KEY_END 79


:)

Hm, das sind aber auch die entsprechenden Buchstaben H,P etc.

Oder kann ich auch abfragen if (getch()==KEY_CURSOR_UP) oder sowas?

Matrix316
2003-10-17, 12:26:32
Original geschrieben von Xmas
Cursortasten haben gar keine "ASCII-Nummer", denn ASCII ist ein Zeichensatz, kein Tastaturlayout. Sie haben allerdings einen entsprechenden Steuercode, ich glaube jedoch der ist bei getch() multibyte, sprich du müsstest mehrere Bytes nacheinander mit getch() abfragen. Das lässt sich in dein bestehendes System kaum integrieren.

edit:
Das oben bezog sich auf getc(). getch() ist Bestandteil von curses.h, was keine ANSI-C Funktionalität ist. In Curses kannst du die Steuercode-Übersetzung mittels keypad() aktivieren.

Und wenn ich nur Ansi-c nutzen will?

Crushinator
2003-10-17, 12:33:54
Original geschrieben von Matrix316
Hm, das sind aber auch die entsprechenden Buchstaben H,P etc.

Oder kann ich auch abfragen if (getch()==KEY_CURSOR_UP) oder sowas? Das sind KeyCodes, und Du kannst sie mit

#define KEY_CURSOR_LEFT 75
...
iKey = getch();
switch (iKey)
{
case KEY_CURSOR_LEFT:
...
break;
}

bzw. so wie Du's selbst geschrieben hast (if (getch()==KEY_CURSOR_UP)) verwenden.

Matrix316
2003-10-17, 12:52:37
Das Problem ist, die Zahlen sind gleichzeitig auch von Buchstaben belegt, das heißt, Cursor nach unten ist gleichzeitig das große H oder P oder sowas. Oder kann ich die Zahlen der Pfeiltasten auch irgendwie umlegen auf andere Zeichen?

Crushinator
2003-10-17, 12:58:49
^^
Wie Xmas schon schrieb: Das sind keine ASCII-Codes sondern scancodes/keycodes, also keine gleichzeitige Belegung der Buchstaben. Bei H bekommt man z.B. 35 zurück. :)
Original geschrieben von Matrix316
Und wenn ich nur Ansi-c nutzen will? Geht genau so. Ich probier's gleich zur Sicherheit auf 'ner UNIX-Kiste aus. Erstmal geh' ich aber happahappa machen. ;)

Matrix316
2003-10-17, 13:15:47
Original geschrieben von crushinator
^^
Wie Xmas schon schrieb: Das sind keine ASCII-Codes sondern scancodes/keycodes, also keine gleichzeitige Belegung der Buchstaben. Bei H bekommt man z.B. 35 zurück. :)


Und wie nutze ich "scancodes/keycodes"? Wenn ich bei meinem Programm die Pfeiltasten drücke erscheint ja als Buchstabe H,P usw.. was laut ASCII Tabelle dezimal 72 bzw. 80 entspricht. ;)

Xmas
2003-10-17, 14:14:05
Original geschrieben von crushinator
Geht genau so. Ich probier's gleich zur Sicherheit auf 'ner UNIX-Kiste aus. Erstmal geh' ich aber happahappa machen. ;)
Nein, geht nicht so. Das von dir beschriebene Verhalten von getch bezieht sich auf getch() aus curses.h (und setzt zudem noch das vorherige verwenden von keypad() voraus). Dies ist aber nicht ANSI C. Es gibt ebenso getch() in den meisten conio.h, dort ist aber erstens das Verhalten anders, und zweitens ist dies auch nicht ANSI C.

Wer ANSI C will, sollte getc() bzw. getchar() aus stdio.h nutzen.

Crushinator
2003-10-17, 14:56:32
Original geschrieben von Xmas
(...)
Wer ANSI C will, sollte getc() bzw. getchar() aus stdio.h nutzen. :o hast recht, ich nutze nämlich eine conio.h und C++ :D

Crushinator
2003-10-17, 16:50:18
Original geschrieben von Matrix316
Und wie nutze ich "scancodes/keycodes"? Wenn ich bei meinem Programm die Pfeiltasten drücke erscheint ja als Buchstabe H,P usw.. was laut ASCII Tabelle dezimal 72 bzw. 80 entspricht. ;) Ja, weil Du die Extended-Codes nicht abfängst. ;)

Ein bißchen Umbau (int ch; statt char ch; und entsprechende Übergabeparameter auch in int) und schon könnte man in der main() sowas machen:


int main()
{
while((ch = getch()) != 27)
{
if (ch != 224) /* Wenn kein Extended-Code*/
{
wdkeypressed(ch);
wdrepaint();
}
else
{
/* darauffolgenden Code sofort abfangen */
switch ((ch = getch()))
{
/* Nur LEFT berücksichtigen */
case KEY_CURSOR_LEFT:
printf("Oh, was war das?\n");
default:
break;
}
}
}
return 0;
}

/edit: Ich habe mir jetzt nicht noch die Mühe gemacht, die ganzen Callbacks um die Behandlung von Extended-Codes zu ergänzen. Das bekommst Du schon selbst hin, denn Du sollst es ja auch schließlich lernen. ;)

Crushinator
2003-10-17, 19:32:23
Ich wurde gerade von einem mitlesenden Kollegen darauf angesprochen, daß es für C-Ungeübte besser wäre, wenn ich das so formulieren würde:

(...)
/* darauffolgenden Code sofort abfangen */
switch ((ch = getch()))
{
case KEY_CURSOR_RIGHT:
printf("Verschieb nach rechts\n");
break;
case KEY_CURSOR_LEFT:
printf("Verschieb nach links\n");
/* break; kann man hier weglassen */
default:
break;
}
(...)

;)

Matrix316
2003-10-17, 20:11:32
In C bin ich schon relativ geübt, die Vorlesung war zwar vor 4 Semestern und dazwischen kam noch C++ und Java aber mit Callback hab ich bislang noch nie was zu tun gehabt. Jedenfalls nicht vorsätzlich. ;)

Crushinator
2003-10-17, 22:26:48
Du warst ja auch nicht gemeint, sondern die in diesem Falle sogar eigenen Freunde bzw. Kollegen, die ich öfters - aus Faulheit das ganze nochmal zu schreiben - nur auf diesen und jenen Thread hinweise. :D

Matrix316
2003-10-18, 11:56:38
Ich glaub ich nehme doch lieber den ascii code. ;)