Calling C Code from C++ With ‘extern “C”‘
source link: https://www.tuicool.com/articles/hit/ueqIbeV
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Now and then we have the need to call functionality that was written in C from our C++ programs. For that, we need to use and understand extern "C"
.
The probably easiest way to use C functionality in a C++ program is to simply compile the C code as C++ code. This will, however, not work reliably. While C++ is based on C, the two languages have their differences. They have even diverged insofar that modern C has features that are not valid C++.
So, we have to compile the C code as C, and the C++ code as C++. Some compilers do this automatically by file extension, others need to be told explicitly. The actual issue is to link the compiled C and C++ object files together.
Linking and name-mangling
Very broadly speaking, the linker has to resolve symbols that are referenced in one or more translation units with their definition in another translation unit. Those symbols can be variable names or function names. For simplicity, let’s assume we have a function void foo(int)
that has been defined in one source file and gets called in another source file.
In C, the compiler generates a simple symbol foo
for that function – this is defined in the C standard. In C++, we can have much more than one function named foo
: we have different namespaces, classes with member functions, and overloaded functions that take different parameters. Therefore, the C++ compiler can not simply create a symbol foo
. It has to generate names that contain all that information. The process is called name mangling
and is not standardized.
Let’s assume, foo
is our C function, that we want to call from main
in our C++ program:
//main.cpp #include "foo.h" int main() { foo(22); }
//foo.h void foo(int);
#include <stdio.h> //foo.c void foo(int i) { printf("%i\n", i); }
When we compile the whole thing, the linker will give us an error: The C++ compiler will see the declaration of void foo(int)
and the call to that function and generate a mangled name, say, void@foo(int)
. The C compiler will simply generate the symbol foo
. The linker will, therefore, complain that it can not find void@foo(int)
, because that symbol simply does not exist.
extern “C” to the rescue
To solve the above problem, the C++ standard allows declaring things with language linkage
. Besides the default C++ linkage, we can explicitly declare things to have “C” linkage. Declaring foo
with “C” linkage will cause the C++ compiler to refer to the name foo
instead of the mangled name. We can declare single entities to have “C” linkage as follows:
extern "C" void foo(int);
More convenient is to declare a whole list of declarations to be of “C” linkage:
extern "C" { void foo(int); //more declarations... }
Note that this is strictly C++ code, as C does not allow the language linkage specification. So, how do we bring all this together without having to rewrite all the C declarations with “C” linkage for the C++ compiler?
The wrong solution
What we see often is, that developers start to alter the C headers as follows:
//foo.h #ifdef __cplusplus extern "C" { #endif void foo(int); #ifdef __cplusplus } //end extern "C" #endif
This will work as intended, as the extern "C"
will be only visible to the compiler. However, it is more than ugly. It infests plain C headers with C++ specific code, which is not desirable. We write that code in C for a reason, usually because it is a library that we’d like to be reused in C programs.
We will have to add these two blocks to any C header that might be used from our C++ program, which may be quite a lot. The C headers may include each other, and while the compiler is OK with encountering several nested levels of extern "C"
, that’s a lot of noise.
The other argument against this practice is that it may not be our responsibility to maintain those C headers. We might not even be able to change them in the future.
The correct solution
Since #include
is a simple text replacement by the preprocessor, we can put the extern "C"
declaration in our C++ code, where it belongs:
//main.cpp extern "C" { #include "foo.h" } int main() { foo(22); }
This way, everything inside the header, including the indirectly included declarations in other C headers, appear inside the extern "C"
declaration.
Caveats
There may be concerns that this looks unfamiliar or even ugly in the C++ code. However, it is still nicer than having the declaration surrounded by #ifdef
s in all our C headers. It also may lead to hard-to-find linker errors when we forget to surround a C header include with the extern "C"
linkage declaration.
Both issues should be minor concerns though if we encapsulate and restrict the use of the C functionality. If we truly have to use the C headers throughout our code base, there’s the option to write a C++ wrapper header for C headers:
//foo_for_cpp.h extern "C" { #include "foo.h" }
//main.cpp #include "foo_for_cpp.h" int main() { foo(22); }
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK