5

Ich habe eine C-Bibliothek, die eine Struktur von Funktionszeigern für Rückrufe verwendet. Die Callbacks werden aus C-Code aufgerufen.Welche Arten von C++ - Funktionen können in einem C-Funktionszeiger platziert werden?

extern "C" { 
typedef struct callbacks_t { 
    void (*foo) (const char*); 
    int (*bar) (int); 
} callbacks_t; 
}// extern C 

Welche Arten von C++ Funktionen kann ich sicher Platz in diesen Funktionszeigern aus der C-Bibliothek aufgerufen werden? Statische Mitgliedsfunktionen? Vollständig spezifizierte Template-Funktionen? Nicht einfangende Lambdas?

g ++ scheinbar lässt mich alle oben genannten verwenden, aber ich frage die Sicherheit, wenn verschiedene Aufrufkonventionen und Sprachenbindungen für C und C++ Funktionen verwendet werden.

+1

_ "aber ich frage die Sicherheit, wenn verschiedene Aufrufkonventionen und Sprachbindungen für C- und C++ - Funktionen verwendet werden" _ Es gibt keine Unterschiede wie diese, wenn alles konsistent für die Zielumgebung kompiliert wurde. –

+3

Statische Member-Funktionen, spezialisierte Template-Funktionen und nicht erfassende Lambdas können nicht extern "C" Aufruf Konvention so formal, das ist nicht gut. Aber in der Praxis kommt es auf den Compiler an.Und in der Praxis ist das Hauptproblem nicht, dass es nicht funktioniert, sondern dass einige Compiler alberne Warnungen über das Formale produzieren können. –

+2

@ Cheersandthth.-Alf, das sollte nicht wichtig sein. Die C-Bibliothek sollte in der Lage sein, eine Funktion aufzurufen, deren Name durch den C++ - Compiler ohne Probleme verändert wird. –

Antwort

3

ich eine C-Bibliothek, die eine Struktur verwendet von Funktionszeiger für Rückrufe. Die Callbacks werden aus C-Code aufgerufen.

AC Bibliothek versteht nur C. So man nur denkt passieren kann zurück, dass

von C. ausdrücklich unterstützt und verstanden werden Da es nicht Definition von Konventionen für alle Funktionen Typen in C++ Aufruf nicht explizit können C++ - Funktionen zurückgeben. Sie können nur die C-Funktion (die explizit mit extern "C" deklarierten) übergeben und garantieren, dass sie kompatibel sind.

Wie alle undefinierte Verhalten scheint dies zu funktionieren (wie normale C++ - Funktionen oder statische Mitglieder zurück). Aber wie jedes undefinierte Verhalten darf es funktionieren. Sie können nur nicht garantieren, dass es tatsächlich korrekt oder tragbar ist.

extern "C" { 
    typedef struct callbacks_t { 
     void (*foo) (const char*); 
     int (*bar) (int); 
    } callbacks_t; 

    // Any functions you define in here. 
    // You can set as value to foo and bar. 

}// extern C 

Welche Arten von C++ Funktionen kann ich in diesen Funktionszeiger stellen sicher aus der C-Bibliothek aufgerufen werden?

Statische Elementfunktionen?

Nein. Aber das passiert auf vielen Plattformen. Dies ist ein häufiger Fehler und wird Menschen auf einigen Plattformen beißen.

Vollständig spezifizierte Vorlagenfunktionen?

Nr

Nicht-Erfassung Lambda?

Nr

g ++ scheinbar mir alle der oben genannten verwenden können,

Ja. Aber es geht davon aus, dass Sie Objekte übergeben, die mit dem C++ - Compiler erstellt wurden (was legal wäre). Als C++ - Code wird die richtige Aufrufkonvention verwendet. Das Problem ist, wenn Sie diese Dinge an eine C-Bibliothek übergeben (Pthreads kommt mir in den Sinn).

+0

Vor einigen Jahren haben Sie erwähnt, dass ein C++ - Compiler mit einer Aufrufkonvention für statische Member gefunden wurde, die sich von der C-Aufrufkonvention unterscheidet: http://stackoverflow.com/questions/1738313/c-using-class-method -as-a-function-pointer-type/1738425 # comment-1618920 Ich weiß, dass es schon lange her ist, aber ich denke, dass alle Details, an die Sie sich erinnern könnten, sehr interessant wären. –

+1

Das war während meiner Zeit bei VERITAS (4 Jobs vor). Ich war verantwortlich für das Build-System, das die Software auf 25 Compiler/OS-Varianten gebaut hat. Leider kann ich mich nicht erinnern, welches es war. Aber diese Erfahrung ist im Grunde das, was mich dazu bringt, mich von allem fernzuhalten, was einem unbestimmten oder undefinierten Verhalten nahe kommt. –

2

Im Allgemeinen sollten Sie, wenn Sie nicht Cast verwenden, g ++ vertrauen.

Es stimmt zwar, dass keiner der von Ihnen genannten Funktionstypen exportiert werden kann, um sie in C zu verwenden. Dies ist jedoch nicht das, was Sie fragen. Sie fragen, welche Funktionen Sie als Funktionszeiger übergeben können.

Um zu beantworten, was Sie passieren können, denke ich, es ist konstruktiver zu verstehen, was Sie nicht passieren können. Sie können nichts übergeben, das zusätzliche Argumente benötigt, die nicht explizit in der Liste der Argumente aufgeführt sind.

Also keine nicht statischen Methoden. Sie brauchen ein implizites "Dies". C wird es nicht weitergeben. Dann wird der Compiler Sie nicht zulassen.

Kein Capturing Lambdas. Sie erfordern ein implizites Argument mit dem tatsächlichen Lambda-Körper.

Was Sie übergeben können, sind Funktionszeiger, die keinen impliziten Kontext erfordern. Als ein Punkt der Tat gingen Sie voran und listeten sie auf:

  • Funktionszeiger. Es spielt keine Rolle, ob es sich um eine Standardfunktion oder eine Vorlage handelt, solange die Vorlage vollständig aufgelöst ist. Das ist kein Problem. Jede geschriebene Syntax, die zu einem Funktionszeiger führt, löst die Vorlage automatisch vollständig auf.
  • Nicht einfangende Lambdas. Dies ist ein spezieller Work-Around, der von C++ 11 eingeführt wurde, als Lambdas eingeführt wurden. Da dies möglich ist, führt der Compiler die explizite Konvertierung durch, die erforderlich ist, um dies zu ermöglichen.
  • Statische Methoden. Da sie statisch sind, werden sie nicht implizit this übergeben, also sind sie in Ordnung.

Der letzte trägt sich erweitert. Viele C-Callback-Mechanismen erhalten einen Funktionszeiger und ein void * opaq. Das Folgende ist ein Standard und ziemlich sicher zu ihrer Verwendung mit einer C++ Klasse:

class Something { 
    void callback() { 
    // Body goes here 
    } 

    static void exported_callback(void *opaq) { 
    static_cast<Something*>(opaq)->callback(); 
    } 
} 

Und dann tun:

Something something; 

register_callback(Something::exported_callback, &something); 

Edited hinzufügen: Der einzige Grund, warum dies funktioniert, ist, weil die C++ Die Aufrufkonvention und die C-Aufrufkonvention sind identisch, wenn keine impliziten Argumente übergeben werden. Es gibt einen Unterschied in der Namensänderung, aber es ist nicht relevant, wenn Sie einen Funktionszeiger übergeben, da der einzige Zweck von Namensfehlern darin besteht, dem Linker zu ermöglichen, die korrekte Funktionsadresse zu finden.

Hätten Sie diesen Trick mit einem Callback versucht, der z. B. eine stdcall- oder Pascal-Aufrufkonvention erwartet, würde dieses Schema flach auf sein Gesicht fallen.

Dies ist jedoch nicht nur für statische Methoden, Lambdas und Vorlagenfunktionen. Selbst eine Standardfunktion würde unter diesen Umständen versagen.

Leider, wenn Sie einen Funktionszeiger auf einen stdcall Typen definieren, gcc ignoriert Sie:

#define stdcall __attribute__((stdcall)) 
typedef stdcall void (*callback_type)(void *); 

Ergebnisse in:

test.cpp:2:45: warning: ‘stdcall’ attribute ignored [-Wattributes] 
typedef stdcall void (*callback_type)(void *); 
+1

Dies ist eine sehr praktische Ansicht. Es ist als solches nicht falsch. Aber im formellen kann man nicht mit gut definierten Verhaltensweisen Konventionen verbinden. Ich denke du solltest auch das Formale erwähnen. Es ist wissenswert. –

+0

Sie haben Recht. Ich habe meine Antwort entsprechend bearbeitet. –

+1

Das ist alles falsch. Sie können keine C++ - Funktion über einen Funktionszeiger an einen C-Code übergeben. Dies liegt daran, dass die Aufrufkonventionen für beide Sprachen vom Standard nicht angegeben werden. Die einzige Sache, die Sie von C++ nach C übertragen können und garantieren, dass es funktioniert, ist C-Funktion (explizit mit 'extern" C "deklariert). Wird es vielleicht brechen, vielleicht nicht wie alles undefinierte Verhalten, so scheint es, dass es perfekt auf Ihrer Plattform funktioniert. –