Using cImport with Zig

zig is a new project I’m rather excited which is positioning itself as a modern C” language. It’s also a system level programming language which makes it a great candidate for embedded systems and microcontrollers. I’ve used Rust in the past for a couple of my projects but it is complex. I personally don’t interact with it enough to remember and write code fluently. From what I’ve seen with zig, its much more intuitive and easier to read and write. I learned on C so I don’t mind the memory management. This is also a great thing to learn anyway if working with microchips.

This post is intended to be simple and a beacon of light for others, as I spent a couple hours on trying to leverage the cImport ability in zig. Remember it is a young project, with interfaces changing and the documentation evolving.

Why do I care about this funcitonality enough to write on it? There is a substantial amount of hardware libraries written in C, which means we should be able to leverage them in zig as well. I’m thinking humidity sensors, LCD and OLEDs, etc. No need to re-write this great foundation of tooling.

Example

I’m using zig stable 0.13.0 in this example and started by creating a new project.

mkdir c-func
cd c-func
zig init

From the initial project structure we are going to add src/add.c and src/add.h which is our C code we’d like to leverage.

// add.c
#include "add.h"

int add(int a, int b) {
    return a + b;
}
// add.h

#ifndef ADD_H
#define ADD_H

int add(int a, int b);

#endif

This is a very simple C function that returns two numbers added together.

Now to the part that I struggled with, building the main.zig with the C code.

In the build.zig we have to make a very simple addition to the exe artifact we’ve created after it’s declaration: exe.addIncludePath(b.path("src"));. There are a number of solutions online that didn’t work for me but this solution simply tells the build instructions to include the src directory (which is a little counter intuitive when the main.zig lives there).

The b.path() is a LazyPath which is one of the errors I kept receiving in my struggle.

Lastly, we need to @cImport the required files in the main.zig so we can build and leverage the C code. After we included the src/ in the build, we can list both .h and .c files here.

const std = @import("std");

const c = @cImport({
    @cInclude("add.h");
    @cInclude("add.c");
});

pub fn main() !void {
    const result = c.add(5, 7);
    std.debug.print("Result of add(5,7): {}\n", .{result});
}

Now we can zig build run:

zig build run
Result of add(5,7): 12

This simple means of including C is very exciting. I hope this helps someone else trying to do the same thing while the documenation and developent on the zig project continues.



Date
November 26, 2024


Previously
brown-trout