Istnieje wiele problemów z przenośnością w C ++, co wynika tylko z braku jego standaryzacji na poziomie binarnym.
Nie sądzę, że to takie proste. Dostarczone odpowiedzi już stanowią doskonałe uzasadnienie braku koncentracji na standaryzacji, ale C ++ może być zbyt bogaty w język, aby nadawał się do autentycznego konkurowania z C jako standardem ABI.
Możemy wejść w mangowanie nazw wynikające z przeciążenia funkcji, niezgodności vtable, niezgodności z wyjątkami, które przekraczają granice modułów itp. Wszystko to jest prawdziwym problemem i żałuję, że nie mogą przynajmniej znormalizować układów vtable.
Ale standard ABI nie polega tylko na tworzeniu dylibów C ++ wyprodukowanych w jednym kompilatorze, który może być używany przez inny plik binarny zbudowany przez inny kompilator. ABI jest używany w wielu językach . Byłoby miło, gdyby mogli przynajmniej pokryć pierwszą część, ale nie ma mowy, żeby C ++ kiedykolwiek naprawdę konkurował z C na poziomie uniwersalnego ABI, tak kluczowym dla tworzenia najbardziej kompatybilnych dylibów.
Wyobraź sobie prostą parę eksportowanych funkcji:
void f(Foo foo);
void f(Bar bar, int val);
... i wyobraź sobie, Foo
i Bar
były klasy ze sparametryzowanymi konstruktorami, konstruktorami kopiującymi, konstruktorami ruchów i nietrywialnymi destruktorami.
Następnie weźmy scenariusz Python / Lua / C # / Java / Haskell / etc. programista próbuje zaimportować ten moduł i użyć go w swoim języku.
Najpierw potrzebowalibyśmy standardu zmieniającego nazwy, aby wyeksportować symbole wykorzystujące przeciążenie funkcji. To jest łatwiejsza część. Jednak tak naprawdę nie powinna to być nazwa „mangling”. Ponieważ użytkownicy dylib muszą wyszukiwać symbole według nazwy, przeciążenia tutaj powinny prowadzić do nazw, które nie wyglądają jak kompletny bałagan. Może nazwy symboli mogą być podobne "f_Foo"
"f_Bar_int"
lub coś w tym rodzaju. Musielibyśmy mieć pewność, że nie mogą kolidować z nazwą faktycznie zdefiniowaną przez programistę, być może rezerwując niektóre symbole / znaki / konwencje na użytek ABI.
Ale teraz trudniejszy scenariusz. Jak na przykład programista Python wywołuje konstruktory przenoszenia, konstruktory kopiowania i destruktory? Może moglibyśmy je wyeksportować jako część dylib. Ale co, jeśli Foo
i Bar
są eksportowane w różnych modułach? Czy powinniśmy powielać symbole i implementacje związane z tą wersją, czy nie? Sugeruję, abyśmy zrobili, ponieważ może to być naprawdę irytujące bardzo szybko, w przeciwnym razie zacznie się zaplątać w wiele interfejsów dylib tylko po to, aby utworzyć tutaj obiekt, przekazać go tutaj, skopiować tutaj, zniszczyć tutaj. Podczas gdy ta sama podstawowa obawa może w pewnym stopniu dotyczyć C (tylko bardziej ręcznie / jawnie), C ma tendencję do unikania tego właśnie ze względu na sposób, w jaki ludzie to programują.
To tylko mała próbka niezręczności. Co się stanie, gdy jedna z f
powyższych funkcji wrzuci BazException
( a także klasę C ++ z konstruktorami i destruktorami i wyprowadzając std :: wyjątek) do JavaScript?
W najlepszym razie myślę, że możemy jedynie mieć nadzieję na standaryzację ABI, który działa z jednego pliku binarnego produkowanego przez jeden kompilator C ++ na inny plik binarny produkowany przez inny. Oczywiście byłoby świetnie, ale chciałem tylko to podkreślić. Zazwyczaj towarzyszy temu obawa związana z rozpowszechnianiem uogólnionej biblioteki, która działa między kompilatorami, jest często chęć, aby była ona naprawdę uogólniona i kompatybilna z wieloma językami.
Sugerowane rozwiązanie
Moje zasugerowane rozwiązanie po wielu latach starań o znalezienie sposobów używania interfejsów C ++ dla interfejsów API / ABI z interfejsami typu COM to po prostu zostać programistą „pun / C ++”.
Użyj C, aby utworzyć te uniwersalne ABI, a C ++ do implementacji. Nadal możemy robić takie funkcje, jak funkcje eksportu, które zwracają wskaźniki do nieprzezroczystych klas C ++ z jawnymi funkcjami do tworzenia i niszczenia takich obiektów na stercie. Spróbuj zakochać się w estetyce C z perspektywy ABI, nawet jeśli do implementacji całkowicie używamy C ++. Abstrakcyjne interfejsy można modelować za pomocą tabel wskaźników funkcji. Pakowanie tego typu rzeczy do C API jest żmudne, ale korzyści i kompatybilność dostarczanej z nim dystrybucji sprawią, że będzie to bardzo opłacalne.
Jeśli więc nie lubimy bezpośrednio używać tego interfejsu (prawdopodobnie nie powinniśmy, przynajmniej z powodów RAII), możemy owinąć to wszystko, czego chcemy, w statycznie połączonej bibliotece C ++ dostarczanej z SDK. Klienci C ++ mogą z tego korzystać.
Klienci w języku Python nie będą chcieli korzystać bezpośrednio z interfejsu C lub C ++, ponieważ nie ma sposobu, aby zrobić z nich pythoniego. Będą chcieli zawinąć to w swoje własne interfejsy pytoniczne, więc tak naprawdę to dobrze, że eksportujemy tylko minimalne C API / ABI, aby było to tak proste, jak to możliwe.
Myślę, że dużo branży C ++ skorzystałoby na tym bardziej, niż uporczywie wysyłając interfejsy w stylu COM i tak dalej. Ułatwiłoby to również całe nasze życie, ponieważ użytkownicy tych dylibów nie musieliby się martwić o niezręczne ABI. C sprawia, że jest to proste, a jego prostota z perspektywy ABI pozwala nam tworzyć interfejsy API / ABI, które działają w sposób naturalny i minimalizujący dla wszystkich rodzajów FFI.