Que fait exactement le fait de placer extern "C"
dans du code C++?
Par exemple:
extern "C" {
void foo();
}
extern "C" crée un nom de fonction en C++ avec une liaison "C" (le compilateur ne modifie pas le nom) de sorte que le code C du client puisse créer un lien vers (utiliser) votre fonction en utilisant un fichier d'en-tête compatible "C" déclaration de votre fonction. La définition de votre fonction est contenue dans un format binaire (compilé par votre compilateur C++) auquel l’éditeur de liens "C" du client se liera ensuite en utilisant le nom "C".
Étant donné que C++ surcharge les noms de fonctions et pas C, le compilateur C++ ne peut pas simplement utiliser le nom de la fonction comme identifiant unique vers lequel se lier, il modifie le nom en ajoutant des informations sur les arguments. Le compilateur AC n'a pas besoin de modifier le nom car vous ne pouvez pas surcharger les noms de fonction en C. Lorsque vous indiquez qu'une fonction a une liaison externe "C" en C++, le compilateur C++ n'ajoute pas d'informations de type argument/paramètre au nom utilisé. lien.
Pour votre information, vous pouvez spécifier explicitement le lien "C" dans chaque déclaration/définition ou utiliser un bloc pour regrouper une séquence de déclarations/définitions afin d’avoir un certain lien:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Si vous vous souciez des aspects techniques, ils sont énumérés à la section 7.5 de la norme C++ 03. Voici un bref résumé (mettant l’accent sur extern "C"):
Je voulais juste ajouter un peu d'information, car je ne l'ai pas encore vu posté.
Vous verrez très souvent le code dans les en-têtes C comme ceci:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
Cela vous permet d’utiliser ce fichier d’en-tête C avec votre code C++, car la macro "__cplusplus" sera définie. Mais vous pouvez aussi toujours l'utiliser avec votre code C hérité, où la macro est PAS définie, de sorte qu'il ne verra pas la construction uniquement C++.
Bien que, j'ai aussi vu le code C++ tel que:
extern "C" {
#include "legacy_C_header.h"
}
que j'imagine accomplit à peu près la même chose.
Je ne sais pas quel chemin est le meilleur, mais j'ai vu les deux.
Décompiler un binaire généré par g++
pour voir ce qu'il se passe
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Compilez et désassemblez la sortie ELF générée:
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
La sortie contient:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
Interprétation
On voit ça:
ef
et eg
étaient stockés dans des symboles portant le même nom que dans le code
les autres symboles ont été mutilés. Démêlons-les:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
Conclusion: les deux types de symboles suivants étaient et non mutilés:
Ndx = UND
), à fournir au moment de la liaison ou de l'exécution à partir d'un autre fichier objetVous aurez donc besoin de extern "C"
à la fois pour appeler:
g++
de s'attendre à ce que des symboles non mélangés soient produits par gcc
g++
de générer des symboles non mélangés pour gcc
à utiliserChoses qui ne fonctionnent pas dans extern C
Il devient évident que toute fonctionnalité C++ nécessitant une modification de nom ne fonctionnera pas dans extern C
:
extern "C" {
// Overloading.
// error: declaration of C function ‘void f(int)’ conflicts with
void f();
void f(int i);
// Templates.
// error: template with C linkage
template <class C> void f(C i) { }
}
C minimal exécutable à partir de l'exemple C++
Par souci d'exhaustivité et pour les débutants, voir aussi: Comment utiliser des fichiers source C dans un projet C++?
Appeler C depuis C++ est assez facile: chaque fonction C n'a qu'un seul symbole possible non mutilé, aucun travail supplémentaire n'est donc nécessaire.
main.cpp
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
c.h
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
c.c
#include "c.h"
int f(void) { return 1; }
Courir:
g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out
Sans extern "C"
le lien échoue avec:
main.cpp:6: undefined reference to `f()'
parce que g++
s'attend à trouver un f
mutilé, que gcc
n'a pas produit.
Exécution minimale de C++ à partir de l'exemple C
L'appel de C++ à partir de C est un peu plus difficile: nous devons créer manuellement des versions non mutilées de chaque fonction à exposer.
Nous illustrons ici comment exposer les surcharges de fonctions C++ en C.
principal c
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
cpp.h
#ifndef CPP_H
#define CPP_H
#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif
#endif
cpp.cpp
#include "cpp.h"
int f(int i) {
return i + 1;
}
int f(float i) {
return i + 2;
}
int f_int(int i) {
return f(i);
}
int f_float(float i) {
return f(i);
}
Courir:
gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out
Sans extern "C"
, il échoue avec:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
parce que g++
a généré des symboles mutilés que gcc
ne peut pas trouver.
Testé dans Ubuntu 18.04.
Dans chaque programme C++, toutes les fonctions non statiques sont représentées dans le fichier binaire sous forme de symboles. Ces symboles sont des chaînes de texte spéciales qui identifient de manière unique une fonction dans le programme.
En C, le nom du symbole est identique au nom de la fonction. Cela est possible car en C, deux fonctions non statiques ne peuvent avoir le même nom.
Parce que C++ autorise la surcharge et a de nombreuses fonctionnalités que le C, à la différence des classes, des fonctions membres et des spécifications d'exception, il n'est pas possible d'utiliser simplement le nom de la fonction en tant que nom de symbole. Pour résoudre ce problème, C++ utilise ce que l'on appelle le nom mangling, qui transforme le nom de la fonction et toutes les informations nécessaires (comme le nombre et la taille des arguments) en une chaîne étrange, traitée uniquement par le compilateur et l'éditeur de liens.
Donc, si vous spécifiez une fonction comme étant extern C, le compilateur n'effectue pas de changement de nom avec elle et il est possible d'y accéder directement en utilisant le nom de son symbole comme nom de la fonction.
Ceci est pratique lorsque vous utilisez dlsym()
et dlopen()
pour appeler de telles fonctions.
La plupart des langages de programmation ne sont pas construits sur les langages de programmation existants. C++ est construit sur le C, et est en outre un langage de programmation orienté objet construit à partir d'un langage de programmation procédural. C'est pourquoi il existe des expressions C++ comme extern "C"
qui fournissent une compatibilité ascendante avec C.
Regardons l'exemple suivant:
#include <stdio.h>
// Two functions are defined with the same name
// but have different parameters
void printMe(int a) {
printf("int: %i\n", a);
}
void printMe(char a) {
printf("char: %c\n", a);
}
int main() {
printMe("a");
printMe(1);
return 0;
}
Un compilateur C ne compilera pas l'exemple ci-dessus car la même fonction printMe
est définie deux fois (même s'ils ont des paramètres différents int a
vs char a
).
gcc -o printMe printMe.c && ./printMe;
1 erreur. PrintMe est défini plusieurs fois.
Un compilateur C++ compilera l'exemple ci-dessus. Peu importe que printMe
soit défini deux fois.
g ++ -o printMe printMe.c && ./printMe;
En effet, un compilateur C++ renomme implicitement ( mangles ) des fonctions en fonction de leurs paramètres. En C, cette fonctionnalité n'était pas supportée. Cependant, lorsque C++ était construit sur C, le langage était conçu pour être orienté objet et devait prendre en charge la possibilité de créer différentes classes avec des méthodes (fonctions) du même nom, et de remplacer des méthodes ( méthode ) en fonction de différents paramètres.
extern "C"
dit "ne modifiez pas les noms de fonctions C"Cependant, imaginons que nous ayons un fichier C hérité nommé "parent.c" qui include
s les noms de fonctions d’autres fichiers C hérités, "parent.h", "child.h", etc. Si l’ancien héritage "parent.c "Le fichier est exécuté via un compilateur C++, puis les noms de fonction seront modifiés et ils ne correspondront plus aux noms de fonction spécifiés dans" parent.h "," enfant.h ", etc. - de sorte que les noms de fonction dans ces fichiers externes aurait également besoin d'être mutilé. La gestion des noms de fonction dans un programme C complexe, ceux qui comportent de nombreuses dépendances, peut conduire à une rupture du code. il peut donc être pratique de fournir un mot clé qui indique au compilateur C++ de ne pas modifier un nom de fonction.
Le mot clé extern "C"
indique à un compilateur C++ de ne pas modifier (renommer) les noms de fonctions C. Exemple d'utilisation: extern "C" void printMe(int a);
Cela change le lien d'une fonction de telle manière que la fonction puisse être appelée à partir de C. En pratique, cela signifie que le nom de la fonction n'est pas mutilé .
Aucun en-tête C ne peut être rendu compatible avec C++ en enveloppant simplement extern "C". Lorsque les identifiants d'un en-tête C sont en conflit avec des mots-clés C++, le compilateur C++ s'en plaint.
Par exemple, j'ai vu le code suivant échouer dans un g ++:
extern "C" {
struct method {
int virtual;
};
}
Kinda a du sens, mais il faut en tenir compte lors du portage du code C en C++.
Il informe le compilateur C++ de rechercher les noms de ces fonctions dans un style C lors de la liaison, car les noms des fonctions compilées en C et C++ sont différents au cours de la phase de liaison.
extern "C" est destiné à être reconnu par un compilateur C++ et à informer le compilateur que la fonction indiquée est (ou doit être) compilée dans un style C. Ainsi, lors de la liaison, le lien vers la version correcte de la fonction à partir de C.
J'ai utilisé 'extern "C"' auparavant pour les fichiers dll (bibliothèque de liens dynamiques) afin de rendre etc. la fonction main () "exportable" afin qu'elle puisse être utilisée ultérieurement dans un autre exécutable à partir de dll. Peut-être qu'un exemple de l'endroit où je l'utilisais peut être utile.
DLL
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
EXE
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
extern "C"
est une spécification de liaison qui est utilisée pour appeler les fonctions C dans fichiers source Cpp. Nous pouvons appeler des fonctions C, écrire des variables et inclure des en-têtes. La fonction est déclarée dans l'entité externe et elle est définie à l'extérieur. La syntaxe est
Type 1:
extern "language" function-prototype
Type 2:
extern "language"
{
function-prototype
};
par exemple:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
Cette réponse est pour les impatients/ont des délais à respecter, seule une partie/simple explication est ci-dessous:
Alors
en C++, avec des noms uniques, identifiant chaque fonction
en C, même sans changement de nom, identifie de manière unique chaque fonction
Pour changer le comportement de C++, c’est-à-dire spécifier que le nom mangling ne devrait pas se produire pour une fonction particulière, vous pouvez utiliser extern "C" avant le nom de la fonction, pour quelle que soit la raison, telle que l'exportation d'une fonction avec un nom spécifique depuis une dll, à utiliser par ses clients.
Lisez les autres réponses pour des réponses plus détaillées/correctes.
Lorsque vous mélangez C et C++ (c'est-à-dire, une. Appelant une fonction C à partir de C++; et b. Appelant une fonction C++ à partir de C), la modification du nom C++ pose des problèmes de liaison. Techniquement, ce problème ne se produit que lorsque les fonctions appelées ont déjà été compilées en binaire (très probablement, un fichier de bibliothèque * .a) à l'aide du compilateur correspondant.
Nous devons donc utiliser extern "C" pour désactiver le nom malchangé en C++.