Zig 编译 C 到 Wasm,做了好像又没做完
周末做了个小实验,用 zig 编译 c 代码到 wasm。我测试了静态库、动态库和可执行程序的编译,以及交叉编译。
结论就是 zig 编译器好像什么都做了,但又没做完。
示例项目
一个最简单的 C 语言项目示例,我通常都是按照以下目录和文件来组织:
/zig-c-wasm
--/src/func.c
--/inc/func.h
--/test/test.c
// func.h 中定义类型和声明方法
#include "func.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 创建结构体 创建释放方法
Student* Student_new() {
Student* newStudent = (Student*)malloc(sizeof(Student));
return newStudent;
}
void Student_free(Student* st) {
if( NULL != st ) {
free( st );
}
}
void Student_name(Student* st, const char* name){
int len = strlen(name);
if( len > 0 && len < 20 ){
strcpy((char *)(st->name), name);
}
}
void Student_age(Student* st, int age){
st->age = age;
}
void Student_score(Student* st, float score){
st->score = score;
}
void Student_print(Student* st){
printf("name %s\n", st->name);
printf("score %.2f\n", st->score);
printf("age %d\n", st->age);
fflush(stdout);
}
// 工具函数
float sumScore(Student* st1, Student* st2) {
return st1->score + st2->score;
}
int aveAge(Student* st1, Student* st2) {
return (st1->age + st2->age)/2;
}
// func.c 中放函数逻辑
#include "func.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 创建结构体 创建释放方法
Student* Student_new() {
Student* newStudent = (Student*)malloc(sizeof(Student));
return newStudent;
}
void Student_free(Student* st) {
if( NULL != st ) {
free( st );
}
}
void Student_name(Student* st, const char* name){
int len = strlen(name);
if( len > 0 && len < 20 ){
strcpy((char *)(st->name), name);
}
}
void Student_age(Student* st, int age){
st->age = age;
}
void Student_score(Student* st, float score){
st->score = score;
}
void Student_print(Student* st){
printf("name %s\n", st->name);
printf("score %.2f\n", st->score);
printf("age %d\n", st->age);
fflush(stdout);
}
// 工具函数
float sumScore(Student* st1, Student* st2) {
return st1->score + st2->score;
}
int aveAge(Student* st1, Student* st2) {
return (st1->age + st2->age)/2;
}
// test.c 中放测试代码
#include <stdio.h>
#include "func.h"
int main(){
Student* st1 = Student_new();
Student_name(st1, "st1");
st1->age = 10;
st1->score = 67.89;
Student_print(st1);
Student* st2 = Student_new();
Student_name(st2, "st2");
st2->age = 20;
st2->score = 23.45;
Student_print(st2);
float sum = sumScore(st1, st2);
int age = aveAge(st1, st2);
printf("sumScore %.2f\n", sum);
printf("aveAge %d\n", age);
fflush(stdout);
}
编译
编译静态库
zig build-lib -lc -Iinc src/func.c --name func
通过指定平台可以实现跨平台交叉编译。
zig build-lib -lc -Iinc -target x86_64-linux-gnu src/func.c --name func
Windows 平台输出结果为 func.lib
,Linux 平台输出结果为 libfunc.a
。
-lc 链接libc 库,因为用到了 printf 等函数。
-I 指定头文件路径
编译动态库
zig build-lib -lc -dynamic -Iinc src/func.c --name func
通过指定平台可以实现跨平台交叉编译。
zig build-lib -lc -Iinc -dynamic -target x86_64-linux-gnu src/func.c --name func
Windows 平台输出结果为 func.dll
,Linux 平台输出结果为 libfunc.so
。
-lc 链接libc 库,因为用到了 printf 等函数。
-I 指定头文件路径
-dynamic 指定输出为动态库
编译执行文件
zig build-exe -lc -Iinc -L./ -lfunc test/test.c --name test
zig build-exe -lc -Iinc -target x86_64-linux-musl -L./ -lfunc test/test.c --name testit
通过指定平台可以实现跨平台交叉编译。
Windows 平台输出结果为 test.exe
,Linux 平台因为输出与目录 test 重名,所以修改命令输出结果为 testit
。
编译到 wasm 库
zig build-lib -lc -Iinc -target wasm32-wasi src/func.c --name funcwasm
通过指定 target 为 wasm32-wasi 就可以编译到 wasi,默认输出结果还是 libfunc.a
跟 linux 下的版本重名了。但是实际上内容不一样,不能混用。所以重名名为 funcwasm,输出文件为 libfuncwasm.a
。
.lib .a 等静态库文件其实就是 .o 文件打包 zip 压缩。用压缩工具解压可以看到 .o 文件。
但是这样的 .a 没法给浏览器用呀,还是需要用 emcc 输出为 wasm 文件和 js 文件。
emcc libfuncwasm.a -o func.js
直接编译可以出结果,但是需要的函数都被优化了。
emcc libfuncwasm.a -o func.js -s EXPORTED_FUNCTIONS=['_Student_new','_Student_name','_Student_age','_Student_score','_Student_print','_Student_free','_sumScore','_aveAge']
这样可以输出需要的函数。输出文件有两个: fun.js
fun.wasm
额外编写一个 html 文件在浏览器中进行测试:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Emscripten:Export1</title>
</head>
<body>
<script>
Module = {};
Module.onRuntimeInitialized = function() { //此时才能获得完整Module对象
console.log(Module);
var st1 = Module._Student_new();
// Module._Student_name( st1, "st1 on web")
Module._Student_age( st1, 10 )
Module._Student_score( st1, 67.89 )
var st2 = Module._Student_new();
// Module._Student_name( st2, "st2 on web")
Module._Student_age( st2, 20 )
Module._Student_score( st2, 77.89 )
// 不生效
st1.age = 30;
st1.score = 89.67;
Module._Student_print(st1)
Module._Student_print(st2)
var score = Module._sumScore( st1, st2 )
var age = Module._aveAge( st1, st2 )
console.log("score", score)
console.log("age", age)
}
</script>
<script src="func.js"></script>
</body>
</html>
编译 wasm 可执行程序
zig build-exe -lc -Iinc -target wasm32-wasi -L./ -lfuncwasm test/test.c --name test
输出 test.wasm
可以直接使用 wasmer 运行:
wasmer test.wasm