I know it sounds a bit crazy about the title, so let me tell you what exactly happened. I will also find the author of the library if it is not solvable at the zig level.
Awhile back, I have asked a question about linking c header that loads a dynamic library (.dll / .so). The solution works and I did manage to use the library to writing something; however, for the sake of convenience, I am thinking of porting the library into zig for my future projects.
The library consists a bunch of dynamic libraries for different os and architectures, along with a header file, so I have layered the library like so and this is the reduced form that represented all the necessary files:
libs
|-- sunvox
|-- windows
| |-- lib_x86_64
| | |-- sunvox.dll
|-- sunvox.c
|-- sunvox.h
src
|-- zunvox.zig
build.zig
build.zig.zon
Inspired by the zaudio build.zig, I have written the following code to build the project:
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
_ = b.addModule("zunvox", .{
.root_source_file = b.path("src/zunvox.zig"),
.target = target,
});
const sunvox = b.addModule("sunvox", .{
.target = target,
.optimize = optimize,
});
const sunvox_lib = b.addLibrary(.{
.name = "sunvox",
.root_module = sunvox,
.linkage = .static,
});
b.installArtifact(sunvox_lib);
sunvox.addIncludePath(b.path("libs/sunvox"));
sunvox_lib.linkLibCpp();
sunvox.addCSourceFile(.{
.file = b.path("libs/sunvox/sunvox.c"),
.flags = &.{
"-DSUNVOX_MAIN",
},
});
Followed by the testing module in the same function:
const test_step = b.step("test", "zunvox tests");
const test_module = b.addModule("test", .{
.root_source_file = b.path("src/zunvox.zig"),
.target = target,
.optimize = optimize,
});
const tests = b.addTest(.{
.name = "zunvox-tests",
.root_module = test_module,
});
tests.linkLibrary(sunvox_lib);
b.installArtifact(tests);
test_step.dependOn(&b.addRunArtifact(tests).step);
In order to build the header, inspired by zaudio, I have added a mock c file like shown (sunvox.c
):
#define SUNVOX_IMPLEMENTATION
#include "sunvox.h"
To ensure everything works before I go through the remaining functions, I have only called the function for loading the dll:
const std = @import("std");
const SvError = error{
FailedToLoadDll,
};
pub fn loadDll() SvError!void {
if (sv_load_dll() < 0) {
return SvError.FailedToLoadDll;
}
}
extern fn sv_load_dll() c_int;
test "init sunvox library" {
try loadDll();
}
And here is the problem: When I build the project right now, the test fails because the load_dll() function in the c header can’t load the share library, but if I put the sunvox.dll in the zig-out/bin, I can observe the following behavior:
- running
zig build test
where the terminal located at the build.zig file can’t identify the .dll file - running
zig build test
where the terminal located at the compiled binaries (after you docd zig-out/bin
) passes the test where the .dll is found.
Based on these findings, the header file and the .dll file is highly dependent on the location of the terminal. For a quick hack, I can copy the .dll file at the location where build.zig is located, but here comes in a couples of problems:
- If I ship this library, this means at the user end, they must manually put the .dll at the project root location to compile their applications, but this is counter intuitive because this makes cross compilation impossible since the library has compiled for different os and architectures where their name are identical.
- if the users change their directory location for their terminal, their test will fail once again because the program can’t find the .dll at their current terminal location.
Meanwhile, I know how to ship the dynamic library into the final binary, by using addIntallFile()
to copy the .dll into zig-out/bin
:
b.getInstallStep().dependOn(&b.addInstallFile(b.path("libs/sunvox/windows/lib_x86_64/sunvox.dll"), "lib/sunvox.dll").step);
But the problem is, since this function can only add files under the zig-out
folder, and it is not designed for putting the dynamic library at the build.zig level, we can’t copy the file back at the build.zig level which we shouldn’t do as well. Besides, if I load that as a zig dependencies using zig fetch
, since the library will be located in the cache folder, at the user perspective, there is no way to find the .dll and copy that to the build.zig level in the first place.
Thus, the questions are:
- Is it possible to let
zig build test
recognize the.dll
or other share libraries during testing phase? - Is it possible to copy the .dll to the
zig-out/bin
when the library is linked as a dependency? - Am I linking the dynamic library wrong for building a library? If so, what is the preferred way to handle .dll or .so at build.zig?