Overview
The focus of this document is on working with @cImport and .h files. Linking via the build system is not covered here.
While C header files can be linked directly to Zig, C++ presents challenges. This is justifiably confusing as C/C++ header files can share the same header extension .h.
Even though C++ has it’s own header extention (.hpp), it is very common to find C++ projects that use a .h extension instead.
The Problem
Using .h files that actually link to .cpp source files or are compiled with a C++ compiler present challenges for the Zig user due to several implementation details such as:
- Name Mangling
- Calling Convention
For more information on calling conventions, please checkout the following StackOverflow post: What are the different calling conventions in C/C++ and what do each mean? - Stack Overflow
Name Mangling according to IBM: Name mangling is commonly used to facilitate the overloading feature and visibility within different scopes. IBM Documentation
These issues can cause Zig to fail at linking functions compiled by a C++ compiler via @cImport at build time.
extern “C”
The C++ keyword extern "C" tells the compiler to treat the function in a C style. This works to our advantage in linking our functions to Zig.
The caveat is that C does not recognize extern "C" as a keyword. Thus anything marked with extern "C" will cause @cImport to complain.
A Solution
One way to solve this is to use a helper macro in your .h files that you wish to link to .cpp sources:
// head.h
#if defined(__cplusplus)
// if we are viewing it in C++ we see: extern "C"
#define EXTERN_C extern "C"
#else
// if we are viewing it in C we see: extern
#define EXTERN_C extern
#endif
// foo is interpreted based on who is viewing it
EXTERN_C void foo(int i, int j);
Then, in our .cpp file, we only use the extern "C" qualifier:
// body.cpp
#include "head.h"
extern "C" void foo(int i, int j) {
// insert mind-blowing function implementation here
}
Now, in main.zig, we can import this file without trouble:
// main.zig
pub const C = @cImport({
@cInclude("/path/to/head.h");
});
// later...
C.foo(42, 42);