Jak poprawnie wywołać funkcję C++ z języka C?¶
Pewnie większość z nas wie, że extern „C” (language linkage) wyłącza dekorowanie nazw funkcji (ang. name mangling) i ustawia external linkage. Kompilator C++ dodaje dodatkowe napisy do nazwy na potrzeby, chociażby, przeciążania funkcji.
A mangled name encodes a function or variable’s name, scope, type, and/or template arguments into a text identifier. This identifier is used as the function’s or variable’s linkage name, to preserve compatibility between C++»s language features (templates, scoping, and overloading) and C linkers.
(źródło: github.com/gcc-mirror/gcc)
Zobaczmy przykład:
void moja_funkcja(int, int) {}
int moja_funkcja(char) {}
int main() {}
A oto wygenerowane symbole (pominąłem nieistotne):
00000000000006a9 T main
000000000000069d T _Z12moja_funkcjac
0000000000000690 T _Z12moja_funkcjaii
Pierwsza funkcja otrzymała nazwę _Z12moja_funkcjaii, natomiast druga _Z12moja_funkcjac. Sposób dekorowania nazw jest zależny od kompilatora.
Kompilator języka C nie stosuje dekorowania nazw, nazwy używane w kodzie źródłowym są identyczne z nazwami wygenerowanych symboli. Zobaczmy to na kolejnym przykładzie:
void inna_funkcja(int a, int b) {}
int main() {}
Wygenerowane symbole (tutaj także pominąłem nieistotne):
0000000000000660 T inna_funkcja
000000000000066d T main
Dekorowanie nazw uniemożliwia linkowanie z kodem wynikowym wygenerowanym za pomocą kompilatora języka C. Jak linkować funkcję języka C++, której nazwa w tablicy symboli jest zależna od kompilatora? Rozwiązaniem jest użycie extern „C”:
extern "C" void moja_funkcja(int, int) {}
extern "C" int moja_druga_funkcja(char) {}
int main() {}
Nazwa jednej z przykładowych funkcji musiała ulec zmianie, gdyż nie mogą istnieć dwie takie same nazwy w tablicy symboli:
00000000000006a9 T main
000000000000069d T moja_druga_funkcja
0000000000000690 T moja_funkcja
Bazując na tych informacjach możemy bez problemu napisać program w języku C wywołujący funkcję języka C++.
Plik funkcja.cpp:
extern "C" void funkcja() {}
Plik main.c:
extern void funkcja();
int main() {
funkcja();
return 0;
}
A gdyby tak funkcja C przyjmowała adres na funkcję?¶
Spójrzmy na przykład.
Plik funkcja.c:
void funkcja( void (*ptr)() ) {
ptr();
}
Plik main.cpp:
extern "C" void funkcja( void (*ptr)() );
void przekazywana() {}
int main() {
funkcja(&przekazywana);
}
Główny kod programu znajduje się w pliku main.cpp kompilowany kompilatorem dla języka C++. Funkcja main() wywołuje funkcja z pliku funkcja.c kompilowanym kompilatorem języka C. funkcja wywołuje funkcję przekazaną jako argument. W tym przykładzie nie ma problemów z linkowaniem.
Problem w tym, że kod źródłowy z pliku funkcja.c jest kodem źródłowym języka C i zapis ptr() wywołuje funkcję przekazaną jako argument tak, jak zwykłą funkcję języka C. Przekazywana funkcja jest natomiast funkcją języka C++ i oczekuje, że będzie wywołana tak jak funkcja języka C++.
Mówiąc precyzyjniej problem dotyczy zgodności konwencji wywołań funkcji C i C++. Dla języka C++ mogła (ale nie musiała) zostać przyjęta inna konwencja niż dla języka C.
Problem rozwiąże dodanie extern „C” do definicji funkcji przekazywana(). Na tej podstawie kompilator C++ będzie wiedział jakiej konwencji wywołań użyć:
Every function type, every function name with external linkage, and every variable name with external linkage, has a property called language linkage. Language linkage encapsulates the set of requirements necessary to link with a module written in another programming language: calling convention, name mangling algorithm, etc.
(źródło: cppreference.com)
Poprawiony przykład z użyciem extern „C”:
Plik funkcja.c:
void funkcja( void (*ptr)() ) {
ptr();
}
Plik main.cpp:
extern "C" void funkcja( void (*ptr)() );
extern "C" void przekazywana() {}
int main() {
funkcja(&przekazywana);
}
Na tego typu problem natkniemy się wykorzystując funkcje języka C, możemy do nich zaliczyć funkcje systemowe (np. te związane z pthreads).