Forum: Compiler & IDEs C++: Warum wird diese Methode nicht aufgerufen?


von Stefan (Gast)


Lesenswert?

Hallo,

ich versuche gerade mir eine Threadklasse für einen STM32 unter CooCox 
zu schreiben. Diese sieht folgendermaßen aus:
1
class Thread {
2
private:
3
  OS_STK task_stk[STACK_SIZE_TASK];
4
protected:
5
  static void taskmeth(void *arg);
6
public:
7
  virtual void Execute(void);
8
  void Init();
9
};
10
11
void Thread::taskmeth(void *arg)
12
{
13
  Thread *obj = (Thread *) arg;
14
15
  obj->Execute();
16
}
17
18
void Thread::Init()
19
{
20
  CoCreateTask((FUNCPtr) taskmeth, this, 0, &task_stk[STACK_SIZE_TASK - 1], STACK_SIZE_TASK);
21
}
22
23
void Thread::Execute(void)
24
{
25
  USART_SendData(USART2, 'W');
26
  char c = 'A';
27
  while (true) {
28
    USART_SendData(USART2, c++);
29
    if (c > 'Z')
30
      c = 'A';
31
    CoTickDelay(100);
32
  }
33
}

Problem: die Methode Execute wird nie erreicht.

Wenn ich Init aufrufe wird wie erwartet taskmeth aufgerufen. Ich sehe 
per Debugger dass "arg" die gleiche Adresse hatte wie vorher "this" in 
Init, die Übergabe scheint also richtig zu funktionieren. Eine sehr 
ähnliche Implementierung unter einem µC mit posixthreads funktioniert. 
Was könnte verkehrt sein?

LG
Stefan

von Jürgen L. (jliegner)


Lesenswert?

Stefan schrieb:
> void Thread::taskmeth(void *arg)
> {
>   Thread *obj = (Thread *) arg;
>
>   obj->Execute();
> }

ich schieße mal ins Blaue weil ich CoOS nicht kenne. Wenn das eine 
C-Implementierung ist, dann sollte die thread-Function auch extern "C" 
deklariert werden:

extern "C"
{
  void taskmeth(void *arg)
  {
    Thread *obj = (Thread *) arg;

    obj->Execute();
  }
}

die statische Funktion aus der Thread-Klasse muss dann raus. Aber wie 
gesagt, nur ein Verdacht.

von Alabama J. (alabamajack)


Lesenswert?

Ist *arg bei der Übergabe sicher schon vom ein Thread-Objekt?
Wenn nicht, dann wandelst du ja ziemlich mist um...

von Alabama J. (alabamajack)


Lesenswert?

und wie schaut die CoCreateTask-Funktion aus?

von Stefan (Gast)


Lesenswert?

Ja CoOs ist eine C Implementierung; aber die extern "C" Geschichte hat 
leider auch nicht geholfen. taskmeth() wird ja angesprungen.

@Florian
arg ist ein Zeiger auf ein Objekt der Klasse Thread, genau wie this. 
Dieser wird als Zeiger auf Thread gecastet und in obj gespeichert. Dann 
wird von obj mit dem Dereferenzierungsoperator -> die Methode 
aufgerufen... so meine Denke... Falsch?

von Jürgen L. (jliegner)


Lesenswert?

Florian La schrieb:
> Ist *arg bei der Übergabe sicher schon vom ein Thread-Objekt?
> Wenn nicht, dann wandelst du ja ziemlich mist um...

Da er dort "this" übergibt ist das ok. Ich denke aber CoCreateTask 
erwartet eine "C"- Funktion und keine (auch wenn static) C++ 
Klassenfunktion.

von Stefan (Gast)


Lesenswert?

CoCreateTask wird vom Framework zur Verfügung gestellt:

#define CoCreateTask(task,argv,prio,stk,stkSz)              \
            CreateTask(task,argv,(prio)|(((stkSz)<<8) &0x000FFF00 ),stk)
extern OS_TID      CreateTask(FUNCPtr task,void *argv,U32 
parameter,OS_STK *stk);

von Jürgen L. (jliegner)


Lesenswert?

Stefan schrieb:
> Dann
> wird von obj mit dem Dereferenzierungsoperator -> die Methode
> aufgerufen... so meine Denke... Falsch?

Hast du keinen Debugger? Was passiert den an der Stelle genau?

von Jürgen L. (jliegner)


Lesenswert?

Stefan schrieb:
> #define CoCreateTask(task,argv,prio,stk,stkSz)              \
>             CreateTask(task,argv,(prio)|(((stkSz)<<8) &0x000FFF00 ),stk)
> extern OS_TID      CreateTask(FUNCPtr task,void *argv,U32
> parameter,OS_STK *stk);

Interessant ist hier wir FUNCPtr definiert ist.

von Stefan (Gast)


Lesenswert?

9          obj->Execute();
080014de:   ldr r3, [r7, #12]
080014e0:   ldr r3, [r3, #0]
080014e2:   ldr r3, [r3, #0]
080014e4:   ldr r2, [r7, #12]
080014e6:   adds r0, r2, #0
080014e8:   blx r3
10        }
080014ea:   mov sp, r7
080014ec:   add sp, #16
080014ee:   pop {r7, pc}
080014f0:   add r0, r0
080014f2:   ands r0, r0


Bis zum blx r3 kann ich steppen, dann schepperts ins Nirvana

von Stefan (Gast)


Lesenswert?

typedef void               (*FUNCPtr)(void*);

von Jürgen L. (jliegner)


Lesenswert?

Stefan schrieb:
> void Thread::Init()
> {
>   CoCreateTask((FUNCPtr) taskmeth, this, 0, &task_stk[STACK_SIZE_TASK -
> 1], STACK_SIZE_TASK);
> }

Sicher des der in der Funktion das Ende vom Stack haben will? Wenn ja, 
dann ticken die Chinesen eigenartig.
Ich hätte den Anfang übergeben, der Rest sollte CoCreateTask machen.

von Stefan (Gast)


Lesenswert?

Ja, ist so in den Beispielen. Das mit dem Stackende übergeben habe ich 
schon öfters auch in anderen Betriebssystemen gesehen, habe aber mal 
ausprobiert den Anfang zu übergeben, dann schepperts gleich. Habe mal 
zum Testen die while schleife direkt nach taskmeth kopiert, da drin 
gehts, nur der obj->Execute() Aufruf spackt :-(

von Jürgen L. (jliegner)


Lesenswert?

Stefan schrieb:
> Bis zum blx r3 kann ich steppen, dann schepperts ins Nirvana

Und in r3 steht auch die Adresse von Thread::Execute?

Auch wenn es hier nichts hilft. Die Threadfunktion muss eine 
"C"-Funktion sein. Wenn du das nicht beachtest wirst du noch so manches 
Wunder bei gemischter C C++ Programmierung erleben. qsort und Konsorten 
sind auch ein gutes Beispiel dafür.

: Bearbeitet durch User
von Stefan (Gast)


Lesenswert?

Naja, zumindest muss es eine statische Methode sein, normale Methoden 
werden wohl anders behandelt... Ich habe noch was beim 
"Ursachenminimieren" herausgefunden:
1
void Thread::Init()
2
{
3
Thread *obj = this;
4
obj->Execute();

Selbst hier funktioniert der Aufruf schon nicht :-(

von Stefan (Gast)


Lesenswert?

...und noch was: Selbst einfach nur "Execute();", ganz ohne 
Objektzeiger, geht nicht! ...wenn ich das "virtual" entferne gehts! Das 
virtual brauche ich später natürlich für die Polymorphie wenn ich 
Klassen von Thread ableite...

von Jürgen L. (jliegner)


Lesenswert?

Stefan schrieb:
> Naja, zumindest muss es eine statische Methode sein, normale Methoden
> werden wohl anders behandelt...

Das hat damit nichts zu tun. Selbst eine einfache Funktion in einer 
cpp-Datei ist was anders als in einer c-Datei. Die Parameter werden 
anders gehändelt.

Stefan schrieb:
> void Thread::Init()
> {
> Thread *obj = this;
> obj->Execute();

Wenn das geht:

void Thread::Init()
{
  Execute();
  ...

dann suchen wir an der falschen Stelle. Gibt es denn von Thread eine 
ordentliche Instanz?

von Jürgen L. (jliegner)


Lesenswert?

Ich tippe mal das liegt an deinem Startup-Code. Wird da die Funktion:

  __libc_init_array();

gerufen? Die ist eigentlich dafür da die Tabellen, die der Compiler eben 
auch für virtuelle Funktionen braucht, zu initialisieren. Dafür müssen 
auch Sektionen im Linker-Script angelegt werden. Macht das CooCox schon 
automatisch? Als ich vor ca. 2 Jahren das letzte mal CooCox probiert 
habe musste man das noch von Hand machen!
So etwa sieht das bei mir aus:

    /* C++ constructors etc */
    . = ALIGN(4);
    KEEP(*(.init))

    . = ALIGN(4);
    __preinit_array_start = .;
    KEEP (*(.preinit_array))
    __preinit_array_end = .;

    . = ALIGN(4);
    __init_array_start = .;
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array))
    __init_array_end = .;

    KEEP(*(.fini));

    . = ALIGN(0x4);
    KEEP (*crtbegin.o(.ctors))
    KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
    KEEP (*(SORT(.ctors.*)))
    KEEP (*crtend.o(.ctors))

    . = ALIGN(0x4);
    KEEP (*crtbegin.o(.dtors))
    KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
    KEEP (*(SORT(.dtors.*)))
    KEEP (*crtend.o(.dtors))
    /* End C++ */
    } > Flash

  /*
   * for exception handling/unwind - some Newlib functions (in common
   * with C++ and STDC++) use this.
   * Use KEEP so not discarded with --gc-sections
   */
  .ARM.extab : ALIGN(4)
    {
    KEEP(*(.ARM.extab* .gnu.linkonce.armextab.*))
    } > Flash
  __exidx_start = .;

  .ARM.exidx : ALIGN(4)
    {
    KEEP(*(.ARM.exidx* .gnu.linkonce.armexidx.*))
    } > Flash
  __exidx_end = .;

  _etext = .;


  /* MAIN DATA SECTION */

: Bearbeitet durch User
von Stefan (Gast)


Lesenswert?

Oh ja, das scheint der richtige Fingerzeig zu sein!

https://www.google.de/url?sa=t&source=web&rct=j&ei=ZYPJU7CMEuem4gS_iYDYCA&url=http://www.coocox.org/forum/images/ckeditorfiles/20131023205634_How_to_use_CPlusPlus_in_CoIDE.pdf&cd=2&ved=0CCIQFjAB&usg=AFQjCNHMghXc4C1H2raFIJ6njkAZuNwrSA

Ist im Nachhinein betrachtet auch logisch; für virtuelle Methoden 
brauchts eine VMT für das dynamische Binden, und die wird nicht 
standardmäßig erzeugt.

Also vielen Dank!

von Stefan (Gast)


Lesenswert?

So, das Linkerscript ist modifiziert, allerdings ist der startupcode bei 
mir dummerweise in Assembler geschrieben, und die verlinkte Anleitung 
einen Beitrag weiter oben geht von C aus. Ich weiß nicht wie ich das 
__libc_init_array(); unterbringen kann, das sehe ich auch in der 
Anleitung nicht :-( wie kommt die eigentlich her?

von old man (Gast)


Lesenswert?

wenn du nicht weiter weißt, ruf sie doch in der main auf:
1
extern "C"
2
{
3
  void __libc_init_array(void); 
4
}
5
6
int main (void)
7
{
8
  __libc_init_array();
9
  ...
10
  ...

Die Funktion selbst sollte in einer der C++ Libs oder Objects vom 
Compiler liegen. Die sind bei mir im Linkerscript so drin (bezieht sich 
auf die gnu-arm-tools) :

GROUP 
(
libgcc.a
libc.a
libm.a
crti.o
crtn.o
crtbegin.o
crtend.o
)

von Stefan (Gast)


Lesenswert?

Ok, in main aufrufen hat geklappt, vielen Dank!

Habe dann noch etwas nach Startupcode gegoogelt, der sieht nun so aus:

/* Call the clock system intitialization function.*/
    bl  SystemInit
    bl __libc_init_array

/* Call the application's entry point.*/
  bl main


Klappt! Virtuelle Methoden funktionieren, alles gut.

Danke & Gruß
Stefan

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.