40

Calling C Code from C++ With ‘extern “C”‘

 5 years ago
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.
neoserver,ios ssh client

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);
}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK