Skip to content

Commit d995ea9

Browse files
authored
post: shipping zig libraries with C ABI
1 parent 5ea8a24 commit d995ea9

1 file changed

Lines changed: 269 additions & 0 deletions

File tree

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
---
2+
.title = "在0.15.2版本使用C ABI模拟Zig ABI",
3+
.date = @date("2026-01-18T21:05:00+0800"),
4+
.author = "艾达爱白糖",
5+
.layout = "post.shtml",
6+
.draft = false,
7+
.custom = {
8+
.mermaid = true,
9+
},
10+
---
11+
12+
白糖:艾达,我怎么才能把我的zig项目发布出去,但是不公开源码呀?
13+
14+
艾达:可以分发二进制库文件和相关符号定义文件(符合C ABI的符号定义),照着这样写就行了。
15+
16+
```zig
17+
// lib.zig
18+
pub const Container = extern struct {
19+
value: c_int,
20+
21+
pub export fn foo(self: *@This()) callconv(.c) void {
22+
// ...
23+
}
24+
};
25+
26+
// project.zig
27+
pub const Container = extern struct {
28+
value: c_int,
29+
30+
pub extern fn foo(self: *@This()) callconv(.c) void;
31+
};
32+
```
33+
34+
白糖:导出为C库呀。但是你这就一个结构,一个函数,这么写当然没什么问题,如果有很多函数,结构呢?
35+
36+
艾达:别忘了zig的`comptime`机制呀,编译时可以自动导出符号并且生成符号定义文件。
37+
38+
```zig
39+
// lib.zig
40+
pub const Container = extern struct {
41+
value: c_int,
42+
43+
pub export fn foo(self: *@This()) callconv(.c) void {
44+
// ...
45+
}
46+
};
47+
48+
comptime {
49+
exports(Container);
50+
}
51+
52+
fn exports(Target: type) void {
53+
const info = @typeInfo(Target);
54+
55+
inline for (info.@"struct".decls) |decl| {
56+
const field = @field(Target, decl.name);
57+
const field_type = @TypeOf(field);
58+
const field_type_info = @typeInfo(field_type);
59+
60+
if (field_type_info == .@"fn") {
61+
@export(&field, .{ .name = decl.name });
62+
}
63+
}
64+
}
65+
```
66+
67+
艾达:如果结构内还有类型定义,可以递归调用,这样就只需要传最外层的类型。怕参数错误,可以在导出前做各种检测来避免。注意这个方法需要函数标记`pub`以及不需要标记`export`。对于`extern union`和`enum(c_int)`也是相似的。
68+
69+
白糖:这里只有导出,那符号定义文件怎么自动生成。而且编译时必须是常量,如果可以把调用了`exports`的类型都收集起来给运行时用就好了。艾达,有什么办法吗?
70+
71+
艾达:编译时收集有些困难。不过办法还是有的,如果`exports`在编译时和生成符号定义文件时是不同的函数是不是就可以解决了。接下来就是zig构建系统的事情,我们要利用其module机制。
72+
73+
艾达:首先我们把项目分成多个模块,分别是库本身、导出符号模块、生成符号定义文件模块和依赖生成文件模块的库本身以及运行生成符号定义文件的模块。
74+
75+
```zig
76+
// lib.zig
77+
const export_mod = @import("export_mod");
78+
79+
pub const ExportedContainer = extern struct {
80+
value: c_int,
81+
82+
pub export fn foo(self: *@This()) callconv(.c) void {
83+
// ...
84+
}
85+
};
86+
87+
comptime {
88+
if (export_mod.export_mode) {
89+
exportAllSymbol();
90+
}
91+
}
92+
93+
pub fn exportAllSymbol() void {
94+
export_mod.exportsymbols(ExportedContainer);
95+
}
96+
97+
// export_symbol.zig
98+
pub const export_mode = true;
99+
100+
pub fn exportsymbols(Target: type) void {
101+
// ...
102+
103+
@export(..., ...) ;
104+
105+
// ...
106+
}
107+
108+
// gen_header.zig
109+
pub const export_mode = false;
110+
111+
pub fn exportsymbols(Target: type) void {
112+
genHeader(Target);
113+
}
114+
115+
// gen_header_main.zig
116+
const lib = @import("lib");
117+
118+
fn main() void {
119+
lib.exportAllSymbol();
120+
}
121+
```
122+
123+
艾达:最后在build.zig中创建依赖。
124+
125+
```=html
126+
<pre class="mermaid">
127+
graph TD
128+
lib_builder --> lib
129+
lib -- 依赖 --- symbol_mod
130+
131+
gen_header --> gen_main
132+
gen_main -- 依赖 --- gen_header_lib[lib: gen_header_lib]
133+
gen_header_lib[lib: gen_header_lib] -- 依赖 --- gen_mod
134+
</pre>
135+
```
136+
137+
```zig
138+
// build.zig
139+
pub fn build2(b: *std.Build) void {
140+
const target = b.standardTargetOptions(.{});
141+
const optimize = b.standardOptimizeOption(.{});
142+
143+
// 构建库以及导出符号
144+
const symbol_mod = b.createModule(
145+
.{
146+
.root_source_file = b.path("export_symbol.zig"),
147+
.target = target,
148+
.optimize = optimize,
149+
},
150+
);
151+
152+
const lib = b.createModule(
153+
.{
154+
.root_source_file = b.path("lib.zig"),
155+
.target = target,
156+
.optimize = optimize,
157+
.imports = &.{
158+
.{
159+
.name = "export_mod",
160+
.module = symbol_mod,
161+
},
162+
},
163+
},
164+
);
165+
166+
const lib_builder = b.addLibrary(.{
167+
.name = "lib",
168+
.root_module = lib,
169+
});
170+
171+
b.installArtifact(lib_builder);
172+
173+
// 生成符号定义文件
174+
const gen_mod = b.createModule(
175+
.{
176+
.root_source_file = b.path("gen_header.zig"),
177+
.target = target,
178+
.optimize = optimize,
179+
},
180+
);
181+
182+
const gen_header_lib = b.createModule(
183+
.{
184+
.root_source_file = b.path("lib.zig"),
185+
.target = target,
186+
.optimize = optimize,
187+
.imports = &.{
188+
.{
189+
.name = "export_mod", //注意名字需要相同
190+
.module = gen_mod,
191+
},
192+
},
193+
},
194+
);
195+
196+
const gen_main = b.createModule(
197+
.{
198+
.root_source_file = b.path("gen_header_main.zig"),
199+
.target = target,
200+
.optimize = optimize,
201+
.imports = &.{
202+
.{
203+
.name = "lib", // 与 gen_header_main 中对应
204+
.module = gen_header_lib,
205+
},
206+
},
207+
},
208+
);
209+
210+
const run = b.addRunArtifact(b.addExecutable(.{
211+
.name = "gen_main",
212+
.root_module = gen_main,
213+
}));
214+
215+
b.getInstallStep().dependOn(&run.step);
216+
}
217+
```
218+
219+
艾达:现在完成了构建库时自动生成相关符号定义文件,实现了分享zig库不公开源码的功能。因为用C ABI所以会有一些限制,例如zig标准库的一些类型不能导出,需要实现C版本的。
220+
221+
艾达:具体生成的函数这里省略了,就是递归类型的`decls`和`fields`字段根据类型输出相应文本。不过zig的`Type`有些限制,生成会有些不完美,比如函数信息就没有参数名,`struct`内的`union`字段不知道初始化的哪个变体等。
222+
223+
白糖:太好了,有什么例子可以看看吗?
224+
225+
艾达:当然!
226+
227+
```zig
228+
// lib
229+
pub const Color = extern struct {
230+
r: u8,
231+
g: u8,
232+
b: u8,
233+
a: u8 = 255,
234+
235+
pub fn init(color: @Vector(4, u8)) callconv(.c) @This() {
236+
return .{
237+
.r = color[0],
238+
.g = color[1],
239+
.b = color[2],
240+
.a = color[3],
241+
};
242+
}
243+
};
244+
245+
// symbol.zig 符号定义文件
246+
pub const Color = extern struct {
247+
r : u8 align(1),
248+
g : u8 align(1),
249+
b : u8 align(1),
250+
a : u8 align(1) = 255,
251+
extern fn main_Color_init(@Vector(4, u8)) callconv(.c) @This();
252+
};
253+
```
254+
255+
艾达:对了,如果不想在构建库的同时生成符号定义文件可以修改build.zig。
256+
257+
```zig
258+
// ...
259+
260+
// b.getInstallStep().dependOn(&run.step);
261+
const run_step = b.step("gen_main", "generate the header.");
262+
run_step.dependOn(&run.step);
263+
264+
// ...
265+
```
266+
267+
艾达:在命令行运行`zig build gen_main`就可以单独生成了。
268+
269+
白糖:太好了,爱你!艾达。

0 commit comments

Comments
 (0)