Folgendes Problem: Ich habe eine Sammlung Quellmodule, die per #define gesteuert Funktionen ein- oder ausschließen. Namen und Adressen dieser Funktionen sollen in eine Namenstabelle eingetragen werden, wenn die jeweilige Funktion im Objektcode enthalten ist. Die Tabelle soll im Flasspeicher angelegt werden. Das soll automatisch zur Generierungszeit passieren. Die Tabelle wird zur Laufzeit von einer Routine durchsucht, die die Funktionsadressen in Namensstrings umsetzt. Es muss also auch die variable Länge der Tabelle zur Laufzeit feststellbar sein. Wir bekommt man das hin?
Mit der Script-/Programmiersprache deines geringsten Misstrauens. Oliver
Alternativ: überall da, wo via #define irgendwas hinzu kommt, kommt auch gleich der Tabelleneintrag mit dazu (bspw. in Form einer eigenen memory section). Das spart es, den ganzen Salat via Postprocessing nachzuverarbeiten, was ja am Ende auf zweimaliges Linken hinaus läuft (einmal, um zu wissen, was alles "an Bord" ist, das zweite Mal, um die mit diesem Wissen gebaute Tabelle dazu zu linken).
Jörg W. schrieb: > Alternativ: überall da, wo via #define irgendwas hinzu kommt, kommt auch > gleich der Tabelleneintrag mit dazu (bspw. in Form einer eigenen memory > section). Genau sowas schwebt mir vor. Wie fange ich das an, ohne zusätzlich ein Linkerscript bauen zu müssen??
Beitrag #7849046 wurde vom Autor gelöscht.
Moriz schrieb: > Wie fange ich das an, ohne zusätzlich ein Linkerscript bauen zu müssen?? Man kann halt nicht alles haben. Ein eigenes Linkerscript brauchst du dafür dann schon. Ist aber m.E. immer noch einfacher als irgendein Postprocessing.
Ich würde das über #defines machen, und mir den ganzen Tabellen-Zirkus sparen. Der Linker macht das ja für dich. Wenn du tatsächlich unterschiedliche Memory-Regions brauchst -was bei den 8bit AVR eher die Ausnahme ist- kannst du ja einfach alle denkbaren Memory-Regions im Linkerscript eintragen, und via #define kontrollieren, welche tatsächlich genutzt werden. Ungenutzte Zuweisungen im Linkerscript, tun ja nicht weh. Ist zwar kein AVR, aber in diesem Projekt kannst du sehen, wie man in einer config.h mit nem Haufen #define kontrolliert, was da gerade gebaut wird.: Beitrag "Re: [STM32/HAL] simples U(S)ART-Library"
:
Bearbeitet durch User
Harry L. schrieb: > Ich würde das über #defines machen, und mir den ganzen Tabellen-Zirkus > sparen. > Der Linker macht das ja für dich. Mir scheint, ich habe mich nicht klar genug ausgedrückt… Ich habe einen Scheduler als Quelltextbibliothek. Für die Task-Tabelle des Schedulers gibt es eine Dumpfunktion, die im Moment eine Adresse-nach-Namensumsetzungsroutine in der Anwendung aufruft. Dafür muss eine Tabelle gepflegt werden, die den Task-Adressen den Klarnamen zuordnet. Die Pflege der Tabelle will ich umgehen, indem ich einfach in jeder Task-Routine einen Macro aufrufe, der den zugehörigen Umsaetzungstabelleneintrag generiert. Die Tabelleneinträge müssen hintereinander in einer Flash-Section landen – das ist Linkers Job. Damit die Suchroutine weiß, wo die Tabelle endet, muss hinter dieser Section eine weitere leere liegen, die die Adresse hinter der Tabelle liefert. Ich habe etwas herum experimentiert: der Compiler nimmt Deklarationen wie diese an:
1 | #define N(name) { name, FSTR(#name) }
|
2 | static const Name __flash1 NameTable[] = { |
3 | N(powerBankRefreshed), |
4 | N(powerBankRefresher), |
5 | };
|
6 | #undef N
|
wobei __flash1 der der allgemeinen Form __flashN entspricht. Wenn ich dahinter einen leeren Array in __flash2 anlege, könnte das funktionieren, wenn die Sections hintereinander liegen. Nur leider finde ich im .map-File dazu überhaupt nichts, weder Sectionnamen, noch Variablennamen. (Dieser .map-File scheint mir überhaupt nicht zu allzuviel nutze zu sein…)
:
Bearbeitet durch User
wieso eigenes Linker-Script? Du brauchst doch nur eine weitere Memory section für Deine Tabelle. Und der Linker liefert Dir nachher Adresse und Länge der Section. Also nur ein paar Zeilen im Script. Wieso brauchst Du überhaupt getrennte Quellen? Wenn Du in ein Sammelfile alle Header einbinden kannst, kann das der Precompiler für Dich erledigen. Wenn es nur eine Handvoll Teilprojekte sind, kannst du auch eine Liste von (Teilprojekt-)Tabellen anlegen. Wenn es Dir nur um das Listen-/Tabellenende geht, mache sie "Nullterminiert" (natürlich nicht wenn der Linker die anordnet).
Bruno V. schrieb: > wieso eigenes Linker-Script? Du brauchst doch nur eine weitere Memory > section für Deine Tabelle. Und der Linker liefert Dir nachher Adresse > und Länge der Section. Also nur ein paar Zeilen im Script. In welchem Skript? > Wieso brauchst Du überhaupt getrennte Quellen? Wenn Du in ein Sammelfile > alle Header einbinden kannst, kann das der Precompiler für Dich > erledigen. Ganz simpel: ich will den Scheduler einfach in ein Projekt aufnehmen, also scheduler.c und scheduler.h In der Anwendung sind dann die Task-Funktionen verstreut. Wenn jede Taskfunktion es schafft, den zugehörigen Umsetzungstabellen-Eintrag in eine spezielle Section zu bugsieren und der Name des Section-Anfangs und -Endes dem Scheduler bekannt ist, dann muss man sich um die Tabelle nicht mehr weiter kümmern. > Wenn es nur eine Handvoll Teilprojekte sind, kannst du auch eine Liste > von (Teilprojekt-)Tabellen anlegen. Nein, solches Zeug will ich tunlichst vermeiden – das wird schnell furchtbar unübersichtlich und ist bald unpflegbar…
:
Bearbeitet durch User
Daniel A. schrieb: > https://www.mikrocontroller.net/articles/C_Linkerhacks_%2B_Modularisierung Danke, das ist, was ich suche.
Moriz schrieb: > static const Name __flash1 NameTable[] = Named Address Spaces? Was haben die damit zu tun? https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html#AVR-Named-Address-Spaces-1
Gibt es irgendwo ein Standard-Linkerskript für avrgcc? Oder ist Section-Skelett irgendwo hart verdrahtet?
Unter /usr/lib/avr/lib/ldscripts liegt eine große Sammlung von Linker-Scripten – nur welcher wird für den 328P benutzt?
Hier: Beitrag "Re: AVR GCC .text is full - ld scripts finden" steht, es sei avr2. Also vermutlich /usr/lib/avr/lib/ldscripts/arv2.x Wie findet man das heraus?
Moriz schrieb: > Nur leider finde ich im .map-File dazu überhaupt nichts, weder > Sectionnamen, noch Variablennamen. (Dieser .map-File scheint mir > überhaupt nicht zu allzuviel nutze zu sein…) Das Map-File ist nützlich zum Debuggen, wenn man Probleme mit dem Linker hat. Das ist ganz sicher nicht dafür da, irgendwie maschinenlesbar zu sein. Die Symboltabelle wäre vielleicht interessanter für dich. Moriz schrieb: > Wie findet man das heraus? Indem du den GCC mit -v startest, dann sagt er dir, welche Backends er so aufruft.
Moriz schrieb: > Ich habe eine Sammlung Quellmodule, die per #define gesteuert Funktionen > ein- oder ausschließen. > Die Tabelle wird zur Laufzeit von einer Routine durchsucht, die die > Funktionsadressen in Namensstrings umsetzt. Wozu diese Umstände? Ein #define läßt sich doch prima schon zur Compilezeit auswerten.
1 | #define MODULE_CMD_GETCALIB "GetCalib" // Diagnostics only. Returns the calbiration signature
|
2 | #define MODULE_CMD_READ_ADC "ReadAdc" // Diagnostics only, doesnt work for muxed ADC.
|
3 | #define MODULE_CMD_GETSTACK "GetStack" // Diagnostics only. Returns stack usage.
|
4 | |
5 | #define CMD_SIZE 20 // for sscanf format, decimal number only !
|
6 | |
7 | typedef char cmd_str_t[CMD_SIZE+1]; |
8 | |
9 | typedef void (*func)(void); |
10 | |
11 | typedef struct |
12 | {
|
13 | cmd_str_t cmd; |
14 | cmd_str_t cmd_short; |
15 | func func; |
16 | } command_t; |
17 | |
18 | command_t const cmd_table[] PROGMEM = |
19 | {
|
20 | #ifdef MODULE_CMD_GETSTACK
|
21 | {MODULE_CMD_GETSTACK "\0", "gst", cmd_get_stack}, |
22 | #endif
|
23 | #ifdef MODULE_CMD_GETCALIB
|
24 | {MODULE_CMD_GETCALIB "\0", "gc", cmd_get_calib_signature}, |
25 | #endif
|
26 | #ifdef MODULE_CMD_READ_ADC
|
27 | {MODULE_CMD_READ_ADC "\0", "ra", cmd_read_adc}, |
28 | #endif
|
29 | {"", "", 0}, |
30 | };
|
:
Bearbeitet durch User
Moriz schrieb: > Nein, solches Zeug will ich tunlichst vermeiden – das wird schnell > furchtbar unübersichtlich und ist bald unpflegbar… Ich hab nen Kollegen, der ist Fan von überall im Code verteilten sscanf() zum Parsen. Mit dem Erfolg, daß gerne mal Schreibfehler passieren oder sogar Namen doppelt vergeben werden bzw. fehlen. Für einen anderen Leser ist es sehr unübersichtlich, wenn die Strings und Aufrufe überall wahllos verteilt sind. Außerdem erzeugt jedes weitere sscanf() auch massig Codeoverhead. In einer Tabelle kann man viel besser auf einheitliche Syntaxregeln achten und Fehler erkennen.
Peter D. schrieb: > Außerdem erzeugt jedes weitere > sscanf() auch massig Codeoverhead. Wieso das? sscanf wird, wie jede Library-Funktion, einmal zum Code dazugelinkt. Ob man es an drei, zehn oder fünfzig Stellen im Code verwendet, ändert an dessen Größe nichts. Ist doch keine inline-Funktion ...
Harald K. schrieb: > Ob man es an drei, zehn oder fünfzig Stellen im Code > verwendet, ändert an dessen Größe nichts. sscanf() Aufrufe bekommen oft sehr viele Parameter übergeben, die auf dem Stack abgelegt werden müssen. Man kann sich mal im Listing anschauen, was für ein Bohei vor jedem Aufruf veranstaltet wird.
1 | cmd_data.args = sscanf_P(buf, PSTR(CMD_FMT CMD_FMT "%g"), |
2 | cmd_data.cmd, cmd_data.par, &cmd_data.val); |
3 | 4890: 2e e6 ldi r18, 0x6E ; 110 |
4 | 4892: 3c e0 ldi r19, 0x0C ; 12 |
5 | 4894: 3f 93 push r19 |
6 | 4896: 2f 93 push r18 |
7 | 4898: c9 e5 ldi r28, 0x59 ; 89 |
8 | 489a: dc e0 ldi r29, 0x0C ; 12 |
9 | 489c: df 93 push r29 |
10 | 489e: cf 93 push r28 |
11 | 48a0: 24 e4 ldi r18, 0x44 ; 68 |
12 | 48a2: 3c e0 ldi r19, 0x0C ; 12 |
13 | 48a4: 3f 93 push r19 |
14 | 48a6: 2f 93 push r18 |
15 | 48a8: 23 e0 ldi r18, 0x03 ; 3 |
16 | 48aa: 3e e0 ldi r19, 0x0E ; 14 |
17 | 48ac: 3f 93 push r19 |
18 | 48ae: 2f 93 push r18 |
19 | 48b0: 9f 93 push r25 |
20 | 48b2: 8f 93 push r24 |
21 | 48b4: 0e 94 1f 45 call 0x8a3e ; 0x8a3e <sscanf_P> |
:
Bearbeitet durch User
Peter D. schrieb: > Mit dem Erfolg, daß gerne mal Schreibfehler > passieren oder sogar Namen doppelt vergeben werden bzw. fehlen. Im Prinzip hast du Recht, aber hier liegt ein spezieller Fall vor, für den das eben gerade nicht gilt, während es für die global zu pflegende Tabelle zutrifft. In schedule.h gibt es bereits einen Macro SCHEDULER_TASK(name), der eigentlich nur syntaktischer Zucker ist und die Lesbarkeit des Codes verbessert:
1 | #define SCHEDULER_TASK(name) void name()
|
Wenn es gelingt, diesen Macro so umzubauen, dass er gleich den entsprechenden Namenstabelleneintrag generiert, dann braucht man keine globale Tabelle mehr, der Code bleibt lesbar und Namensänderungen schlagen sofort auf die Tabelle durch. Zusätzlich kann man auch zwei Versionen davon bauen, eine mit eingebautem Tabellen-Dump und einen ohne und die ganze Chose über ein in der Kommandozeile des Compilers angebbares Symbol steuern. Dann wäre das alles völlig automatisiert und trotzdem transparent. Die Tabelleneintragsgenerierung kann man mit dem Trick mit der verzeigerten Liste aus https://www.mikrocontroller.net/articles/C_Linkerhacks_%2B_Modularisierung dezentralisieren. (Das mit den Sections funktioniert beim AVR nach dem Artikel nicht, weil die Reihenfolge der Sections nicht definiert ist.)
Jörg W. schrieb: > Die Symboltabelle wäre vielleicht interessanter für dich. Danach habe ich gesucht… Wie krieg ich die?
Moriz schrieb: > Jörg W. schrieb: >> Die Symboltabelle wäre vielleicht interessanter für dich. > > Danach habe ich gesucht… Wie krieg ich die? avr-nm (mit verschiedenen Optionen) Oder programmatisch mit einer ELF-Bibliothek.
Wieso gibt es eigentlich nur leere man-Eintrage für das avr-* Zeug?
Moriz schrieb: > Wenn es gelingt, diesen Macro so umzubauen, dass er gleich den > entsprechenden Namenstabelleneintrag generiert Das Problem an C-Macros ist, daß es nur eine Textersetzung durchführt. Ein Macro kann also nicht weitere Macros erzeugen und es sind auch keine nested Macros möglich. Sowas kann nur der AVR Assembler.
Moriz schrieb: > Das mit den Sections funktioniert beim AVR nach dem Artikel nicht, weil > die Reihenfolge der Sections nicht definiert ist Das eigentliche Problem beim AVR ist das linker script. Die custom sections kommen nicht in .text oder .data rein, die Objekte landen dann nicht auf dem AVR. Und wenn man seine Sections z.B. .data.bla nennt, dann sollte es zwar in .data rein kommen, aber dann generiert der Linker die _start und _stop symbole nicht mehr, die gibts nur wenn der Sektionsname ein gültiger c identifier wäre, und "." kann man da halt nicht haben. Eine echt blöde Situation...
Peter D. schrieb: > Ein Macro kann also nicht weitere Macros erzeugen und es sind auch keine > nested Macros möglich. Muss es auch nicht. Sieh dir den Artikel an, den Daniel A. hier Beitrag "Re: avrgcc: verteilte Array-Initialisierung" verlinkt hat… Ein Problem könnte es geben, wenn in zwei Quellmodulewn der Macro zufällig auf gleichen Zeilennummern aufgerufen wird. Das könnte man aber leicht beheben, z.B. mit einer für jeden Quellfile individuellen Modul-ID – das habe ich in meinem Projekt auch bereits eingebaut, um fatale Fehler mit einer LED blinkend zu kommunizieren.
:
Bearbeitet durch User
Moriz schrieb: > Wieso gibt es eigentlich nur leere man-Eintrage für das avr-* Zeug? Das musst du denjenigen fragen, der das bei dir zusammen gepackt hat. Ich habe sowohl auf einem Ubuntu als auch auf dem FreeBSD dort reale Einträge. Aber: was die Symboltabelle anbelangt, da kannst du auch einfach "man nm" machen.
Jörg W. schrieb: > Aber: was die Symboltabelle anbelangt, da kannst du auch einfach "man > nm" machen. Unter Mint 21 kommt da ein leerer man-Eintag.
Peter D. schrieb: > Das Problem an C-Macros ist, daß es nur eine Textersetzung durchführt. Das wird in dem zitierten Artikel mit dem alten CPP CONCAT-Trick umgangen. Womöglich kannst du da noch was lernen ;-) Neue Macros in einem Macro erzeugen geht nicht, aber das braucht man hier auch nicht. Für solche Schweinereien muss dann der olle m4 her… Dessen Syntax ist allerdings gewöhnungsbedürftig.
:
Bearbeitet durch User
Moriz schrieb: > Jörg W. schrieb: >> Aber: was die Symboltabelle anbelangt, da kannst du auch einfach "man >> nm" machen. > > Unter Mint 21 kommt da ein leerer man-Eintag. Da musst du dich wohl bei den Mint-lern bedanken gehen. Hast du mal
1 | env LANG=C man ld |
probiert?
Jörg W. schrieb: > Hast du malenv LANG=C man ld > probiert? Damit geht es, Teufel… Aber es geht auch ohne Präfix… Für das avr-*-Zeug hilft das nichts.
:
Bearbeitet durch User
Moriz schrieb: > Hier: Beitrag "Re: AVR GCC .text is full - ld scripts finden" steht, es sei > avr2. > > Also vermutlich /usr/lib/avr/lib/ldscripts/arv2.x avr2 auf gar keinen Fall. Das ld script trägt den Namen der Emulation, also was im Linkeraufruf auf -m folgt:
1 | (echo "" | avr-gcc -xc - -r -mmcu=atmega328p -Wl,-v 2>&1) | grep -o -- ' -mavr[a-z_0-9]*\b' | sed -e 's: -m::' |
2 | avr5 |
Das verwendete ld Script ist also avr5.x. Erklärung: Es wird ein leeres Programm mit -Wl,-v gelinkt. Der Linker zeigt mit -v wie er aufgerufen wurde:
1 | collect2 version $version |
2 | $prefix/bin/../lib/gcc/avr/$version/../../../../avr/bin/ld ... -mavr5 ... |
3 | GNU ld (GNU Binutils) $version |
grep und sed filtern dann die Emulation raus. Zur Referenz sind die ld Scripte installiert unter $prefix/avr/lib/ldscript.
Moriz schrieb: > Jörg W. schrieb: >> Hast du malenv LANG=C man ld >> probiert? > > Damit geht es, Teufel… Normalerweise sollte das man-Kommando natürlich auf die englische Version zurück fallen, wenn keine in der Regionalsprache installiert ist. Da scheint also generell was bei dem System kaputt zu sein. > Für das avr-*-Zeug hilft das nichts. Das wiederum dürfte an demjenigen liegen, der die avr-* Pakete gebaut hat. Aber von avr-libc abgesehen (die via "avr-man" bei Bedarf einen Sack voller eigener man pages installieren kann), sind die man pages von GCC und binutils natürlich eh die gleichen wie die ohne "avr-".
:
Bearbeitet durch Moderator
Peter D. schrieb: > sscanf() Aufrufe bekommen oft sehr viele Parameter übergeben, die auf > dem Stack abgelegt werden müssen. Man kann sich mal im Listing > anschauen, was für ein Bohei vor jedem Aufruf veranstaltet wird. Mit ziemlicher Sicherheit gibts noch viel mehr Bohei, wenn man das, was ein sscanf-Aufruf macht, zu Fuß in einzelne Funktionsaufrufe auflöst. Aber wegen einem einzigen sscanf den ganzen Modul zu laden, sollte man sich im Zweifelsfall schon gut überlegen.
:
Bearbeitet durch User
Jetzt habe ich mal so ein Macro gestrickt:
1 | #ifndef DUMP_TASK_TABLE
|
2 | #define SCHEDULER_TASK(n) static void n()
|
3 | #else
|
4 | typedef struct Schd_NameTableEntry { |
5 | const void *taskAddr; |
6 | const char const __flash *name; |
7 | struct Schd_NameTableEntry *next; |
8 | } Schd_NameTableEntry; |
9 | |
10 | #ifndef CONCAT
|
11 | #define CONCAT_EVAL(a, b) a ## b
|
12 | #define CONCAT(a, b) CONCAT_EVAL(a, b)
|
13 | #endif // CONCAT
|
14 | |
15 | #define SCHEDULER_TASK(n) \
|
16 | static void n(); \
|
17 | static Schd_NameTableEntry CONCAT( n, _Schd_NameTableEntry) = { \
|
18 | (n), FSTR(#n), NULL}; \
|
19 | \
|
20 | extern Schd_NameTableEntry *schd_NameTable; \
|
21 | \
|
22 | static void CONCAT( n, _Schd_NameTableEntry_ctor )() \
|
23 | __attribute__((constructor)); \
|
24 | static void CONCAT( n, _Schd_NameTableEntry_ctor )(){ \
|
25 | CONCAT( n, _Schd_NameTableEntry ).next = schd_NameTable; \
|
26 | schd_NameTable = &CONCAT( n, _Schd_NameTableEntry ); \
|
27 | } \
|
28 | \
|
29 | static void n()
|
30 | #endif // DUMP_TASK_TABLE
|
Für den folgenden Aufruf
1 | SCHEDULER_TASK(initReadSensors) { |
2 | // Code der Taskroutine
|
3 | }
|
generiert der C-Preprozessor diesen (handformatierten) Code:
1 | static void initReadSensors(); |
2 | |
3 | static Schd_NameTableEntry initReadSensors_Schd_NameTableEntry = { |
4 | (initReadSensors), |
5 | ((const __flash char[]) { "initReadSensors" } ), |
6 | ((void *)0) |
7 | };
|
8 | |
9 | extern Schd_NameTableEntry *schd_NameTable; |
10 | |
11 | static void initReadSensors_Schd_NameTableEntry_ctor() __attribute__((constructor)); |
12 | static void initReadSensors_Schd_NameTableEntry_ctor(){ |
13 | initReadSensors_Schd_NameTableEntry.next = schd_NameTable; |
14 | schd_NameTable = &initReadSensors_Schd_NameTableEntry; |
15 | }
|
16 | |
17 | static void initReadSensors() { |
18 | // Code der Taskroutine
|
19 | }
|
Das sieht schon mal ganz gut aus… nur finde ich die Funktion initReadSensors_Schd_NameTableEntry_ctor() weder in der Symboltabelle, noch im .map-File. Irgend was scheint da mit der Behandlung von Konstruktoren nicht zu stimmen.
Ursache für die fehlende initReadSensors_Schd_NameTableEntry_ctor() ist Linker-Optimierung. Wenn ich die Option -flto entferne, sind sie im Objekt enthalten. Gibt es eine Möglichkeit, dem Linker mitzuteilen, dass er eine Funktion von der Optimierung ausnehmen soll?
Daniel A. schrieb: > Versuch es mal mit >
1 | __attribute__((constructor,used)) |
Damit funktioniert es. Der gesamte Definitionsblock sieht dann so aus:
1 | #ifndef DUMP_TASK_TABLE
|
2 | #define SCHEDULER_TASK(n) static void n()
|
3 | #else
|
4 | typedef struct Schd_NameTableEntry { |
5 | const void *taskAddr; |
6 | const char const __flash *name; |
7 | struct Schd_NameTableEntry *next; |
8 | } Schd_NameTableEntry; |
9 | |
10 | #ifndef CONCAT
|
11 | #define CONCAT_EVAL(a, b) a ## b
|
12 | #define CONCAT(a, b) CONCAT_EVAL(a, b)
|
13 | #endif // CONCAT
|
14 | |
15 | #define SCHEDULER_TASK(n) \
|
16 | static void n(); \
|
17 | static Schd_NameTableEntry CONCAT( n, _Schd_NameTableEntry) = { \
|
18 | (n), FSTR(#n), NULL }; \
|
19 | \
|
20 | extern Schd_NameTableEntry *schd_NameTable; \
|
21 | \
|
22 | static void CONCAT( n, _Schd_NameTableEntry_ctor )() \
|
23 | __attribute__((constructor,used)); \
|
24 | static void CONCAT( n, _Schd_NameTableEntry_ctor )(){ \
|
25 | CONCAT( n, _Schd_NameTableEntry ).next = schd_NameTable; \
|
26 | schd_NameTable = &CONCAT( n, _Schd_NameTableEntry ); \
|
27 | } \
|
28 | \
|
29 | static void n()
|
30 | #endif // DUMP_TASK_TABLE
|
Die Funktionsdefinition für eine Task:
1 | SCHEDULER_TASK(machWas) { |
2 | // …
|
3 | }
|
Je nachdem, ob das Symbol DUMP_TASK_TABLE definiert ist, oder nicht, wird ein Namenstabelleneintrag generiert, oder nicht.
:
Bearbeitet durch User
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.