@cImport Include Compatibility with C/C++

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);
3 Likes

Thanks goes to @slonik-az for originally suggesting this approach.