Es gibt viele Portabilitätsprobleme mit C ++, die nur auf die fehlende Standardisierung auf Binärebene zurückzuführen sind.
Ich denke nicht, dass es so einfach ist. Die Antworten liefern bereits hervorragende Gründe für die mangelnde Konzentration auf die Standardisierung, aber C ++ ist möglicherweise zu sprachenreich, um wirklich mit C als ABI-Standard konkurrieren zu können.
Wir können auf die Namensverfälschung eingehen, die sich aus Funktionsüberladung, Inkompatibilitäten von V-Tabellen, Inkompatibilitäten mit Ausnahmen über Modulgrenzen hinweg usw. ergibt. All dies ist ein echtes Problem, und ich wünschte, sie könnten zumindest die Layouts von V-Tabellen standardisieren.
Bei einem ABI-Standard geht es jedoch nicht nur darum, C ++ - Dylibs, die in einem Compiler erstellt wurden, so zu machen, dass sie von einer anderen Binärdatei verwendet werden können, die von einem anderen Compiler erstellt wurde. ABI wird sprachenübergreifend verwendet . Es wäre schön, wenn sie zumindest den ersten Teil abdecken könnten, aber ich sehe keine Möglichkeit, dass C ++ jemals wirklich mit C auf einer universellen ABI-Ebene konkurriert, die für die Herstellung der am weitesten kompatiblen Dylibs so wichtig ist.
Stellen Sie sich ein einfaches Funktionspaar vor, das wie folgt exportiert wird:
void f(Foo foo);
void f(Bar bar, int val);
... und sich vorstellen , Foo
und Bar
waren Klassen mit parametrisierte Konstrukteure, Kopierkonstruktoren, verschieben Bauer, und nicht-triviale Destruktoren.
Nehmen Sie dann das Szenario eines Python / Lua / C # / Java / Haskell / etc. Entwickler, der versucht, dieses Modul zu importieren und in seiner Sprache zu verwenden.
Zuerst benötigen wir einen Namensverwaltungsstandard für den Export von Symbolen unter Verwendung von Funktionsüberladung. Dies ist ein einfacher Teil. Dabei sollte es eigentlich nicht "Mangeln" heißen. Da Benutzer der Dylib Symbole nach Namen suchen müssen, sollten die Überladungen hier zu Namen führen, die nicht wie ein vollständiges Durcheinander aussehen. Vielleicht könnten die Symbolnamen so "f_Foo"
"f_Bar_int"
oder so ähnlich sein . Wir müssten sicherstellen, dass sie nicht mit einem vom Entwickler definierten Namen in Konflikt geraten können, und möglicherweise einige Symbole / Zeichen / Konventionen für die ABI-Verwendung reservieren.
Aber jetzt ein schwierigeres Szenario. Wie ruft der Python-Entwickler beispielsweise Move-Konstruktoren, Copy-Konstruktoren und Destruktoren auf? Vielleicht könnten wir diese als Teil der Dylib exportieren. Was aber, wenn Foo
und Bar
in verschiedenen Modulen exportiert werden? Sollten wir die in dieser Dylib enthaltenen Symbole und Implementierungen duplizieren oder nicht? Ich würde vorschlagen, dass wir dies tun, da es sonst sehr schnell nervig werden könnte, sich in mehreren Dylib-Schnittstellen zu verfangen, nur um hier ein Objekt zu erstellen, es hier zu übergeben, eines dort zu kopieren, es hier zu zerstören. Während das gleiche grundlegende Anliegen in C (nur manuell / explizit) etwas zutreffen könnte, tendiert C dazu, dies aufgrund der Art und Weise, wie die Leute damit programmieren, zu vermeiden.
Dies ist nur ein kleines Beispiel für die Unbeholfenheit. Was passiert, wenn eine der f
oben genannten Funktionen eine BazException
(auch eine C ++ - Klasse mit Konstruktoren und Destruktoren und Ableitung der std :: exception) in JavaScript wirft ?
Ich denke, wir können bestenfalls hoffen, ein ABI zu standardisieren, das von einer Binärdatei, die von einem C ++ - Compiler erzeugt wird, zu einer anderen Binärdatei, die von einem anderen erzeugt wird, funktioniert. Das wäre natürlich großartig, aber ich wollte nur darauf hinweisen. In der Regel geht mit solchen Überlegungen zur Verteilung einer generalisierten Bibliothek, die compilerübergreifend funktioniert, auch der Wunsch einher, eine wirklich generalisierte und kompatible sprachübergreifende Bibliothek zu erstellen.
Vorgeschlagene Lösung
Meine vorgeschlagene Lösung, nachdem ich jahrelang Mühe hatte, Möglichkeiten zu finden, C ++ - Schnittstellen für APIs / ABIs mit COM-ähnlichen Schnittstellen zu verwenden, besteht darin, einfach ein "C / C ++" - (Wortspiel-) Entwickler zu werden.
Verwenden Sie C, um diese universellen ABIs zu erstellen, mit C ++ für die Implementierung. Wir können weiterhin Dinge wie Exportfunktionen ausführen, die Zeiger auf undurchsichtige C ++ - Klassen mit expliziten Funktionen zurückgeben, um solche Objekte auf dem Heap zu erstellen und zu zerstören. Versuchen Sie, sich aus ABI-Sicht in diese C-Ästhetik zu verlieben, auch wenn wir C ++ für die Implementierung verwenden. Abstrakte Schnittstellen können mit Tabellen von Funktionszeigern modelliert werden. Es ist mühsam, dieses Zeug in eine C-API zu packen, aber die damit verbundenen Vorteile und die Kompatibilität der Distribution werden es in der Regel sehr lohnenswert machen.
Wenn wir diese Schnittstelle dann nicht so gerne direkt verwenden (wir sollten dies wahrscheinlich nicht aus RAII-Gründen tun), können wir alles, was wir wollen, in eine statisch verknüpfte C ++ - Bibliothek packen, die wir mit dem SDK ausliefern. C ++ Clients können das nutzen.
Python-Clients möchten weder eine C- noch eine C ++ - Schnittstelle direkt verwenden, da es keine Möglichkeit gibt, diese Pythonique zu erstellen. Sie werden es in ihre eigenen Pythonique-Interfaces packen wollen, also ist es eigentlich eine gute Sache, dass wir nur ein Minimum an C API / ABI exportieren, um das so einfach wie möglich zu machen.
Ich denke, ein Großteil der C ++ - Industrie würde davon mehr profitieren, als hartnäckig zu versuchen, Schnittstellen im COM-Stil und so weiter zu liefern. Es würde uns als Benutzer dieser Dylibs auch das ganze Leben leichter machen, uns nicht mit umständlichen ABIs herumschlagen zu müssen. C macht es einfach, und die Einfachheit aus ABI-Sicht ermöglicht es uns, APIs / ABIs zu erstellen, die für alle Arten von FFIs natürlich und minimalistisch funktionieren.