How to use a dynamic library generated by Zig-lang in C++ codebase
In the past, I wrote a similar story about using a dynamic library with C++ and Python but it was in Turkish. Please write a comment or do not hesitate to contact me on any social media account if you want to make me translate it. Today, we will try to produce a dynamic library by using Zig programming language afterwards use it in C++ code. Let’s begin!
Before explaining an implementation detail of this codebase, maybe I should introduce my new favorite programming language Zig. Zig is a freshly-started programming language that encourages you to write metaprogramming style based on compile-time code. Its syntax is similar to Go language at least for me but it has many many unique and significant feature sets that I can explain in another story such as comptime, independent from Glibc, zero dependency, drop-in C/C++ compiler support and so on. Currently, it supports cross-platform compilation and could be installed from many different package managers that you can find them here.
In Zig, there are many different options and you can list them simply by writing “Zig” in the terminal. If you want to create an executable project from scratch you may want to use “zig init-exe”. On the other hand, if you want to create a library project, you can directly use “zig init-lib” command as you can see screenshot below.
If you choose & run “zig init-lib” command above, Zig will generate build.zig file in the current directory and newly created “src” folder inside with main.zig file. In this simple example, we do not have any interaction with build.zig file which is mostly related to compiling, linking and testing configurations. We should go inside main.zig file and try to understand what is written by default.
By default, you will see this content in the main.zig file and I will try to explain line by line. The first line, reminds you what? Oh, same for me. It is like using directive of C# or a combination of using directive and include preprocessor of C++. The second line is a great example of alias usage. It simplifies the usage of testing functionality which is supported by language standards.
Afterwards, you can guess that it is a function call that takes two i32 type parameters and return the sum of them in the same type. “fn” is a keyword for representing a function (or could be an abbreviation), and afterwards “add” is its name. Then parameter syntax is given like “parameter name-colon-type” and i32 equivalent of int32_t C type. The return type is located after these parameters as Go language does. If you are familiar with Golang, you might say that they are almost similar except for colons of parameters and keyword name choice for functions.
Test is another key feature of Zig language, you can directly use it in the source code. “test” keyword is standing for test case and afterwards you can give a name for the case. If you are familiar with unit test frameworks like GTest or Catch2 from C++, the next line is simply understandable, right?
In-depth explanation, you might realize that one thing I did not mention yet intentionally. Oh right, “export” keyword. With the new example below, I will explain it in more detail.
Let’s assume that there are almost similar two function calls named “add_internal” and “add_external”. The second function has another keyword “export” which makes it publicly available. Let’s compile this code block and generate a dynamic library.
To generate a dynamic library from a file, you have to give “-dynamic” option explicitly because Zig generates a static library by default. So, after running the “zig build-lib -dynamic {filename}” command, we will have a dynamic library. Let’s check that our function call is located in the symbol table of the dynamic library. As seen in the screenshot below, “add_external” is located in the symbol table but “add_internal” is not, as we expected.
Now, we are ready to use it in C++ code. Let’s write a simple C++ code to use function immediately which was written in Zig. Here is the simple a few line C++ code to compile & run.
Now, everything is OK, right? Oops, compile stage seems OK but linkage we have a problem here. Why?
A few minutes later… Oh, right mangling! Compile stage was OK because there is no conflict at all for function names. However, “ld” could not find the implementation details of this function because the function name was mangled. Then, the next question is how we can disable mangling for this function? Hello, “extern C” my old friend. Let’s modify our function signature by adding this keyword in front of it like below.
Now, we know that our function signature will not be mangled anymore and we can use it as it is. Let’s compile & run it again without any error.
Perfect! Now, we are ready to use our implementation in C++ code base. I hope that you also enjoy from this journey. In next story, I will try to explain more attractive functionality from Zig language. Please share your thoughts about this story here or any place where you can find me!