C语言编译WebAssembly

本文最后更新于:2024年4月25日 上午

安装Emscripten编译器

这里使用的是win11环境下的安装

1
2
3
4
5
6
7
8
git clone https://github.com/emscripten-core/emsdk.git
cd ./emsdk
git pull

./emsdk install latest
./emsdk activate latest

emsdk_env.bat

检验是否安装成功, 新打开一个终端执行

1
2
3
4
5
emcc --version
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.36 (518d9fea335f7cd2b5771e43df76e0535c6df5dd)
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

若提示找不到命令则将安装目录\emsdk\upstream\emscripten添加至系统环境变量

进行c的单文件编译与调用

hello.c

1
2
3
4
5
6
#include <stdio.h>

int main () {
printf("🌈Hello World!");
return 0;
}

编译为wasm

1
emcc hello.c -o hello.js

此时即可看到当前目录生成了hello.wasmhello.js文件,使用node hello运行hello.js会看到有警告

stdio streams had content in them that was not flushed. you should set EXIT_RUNTIME to 1 (see the FAQ), or make sure to emit a newline when you printf etc.

(this may also be due to not including full filesystem support - try building with -sFORCE_FILESYSTEM)

提示要在输出后加上换行符

1
2
3
4
5
6
 #include <stdio.h>

int main () {
+ printf("🌈Hello World!\n");
return 0;
}

再次编译运行即可看到🌈Hello World!

定义C函数

emcc默认只会保留main函数, 需要使用EMSCRIPTEN_KEEPALIVE来声明该方法需要导出, 为了使C程序可以独立运行亦可编译为wasm后运行,这里参考js的模块风格编写了C语言的预处理

使用emcc时会有宏定义__EMCRIPTEN__, 若有定义则引入emscripten.h头文件

宏定义export(T)

lib.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef export
# if defined(__EMSCRIPTEN__)
# include <emscripten.h>
# define export(T) T EMSCRIPTEN_KEEPALIVE
# else
# define export(T) T
# endif
#endif

export(int) add(int a, int b) {
return a + b;
}

下面是C/C++的通用版预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef export
# if defined(__EMSCRIPTEN__)
# include <emscripten.h>
# if defined(__cplusplus)
# define export(T) extern "C" T EMSCRIPTEN_KEEPALIVE
# else
# define export(T) T EMSCRIPTEN_KEEPALIVE
# endif
# else
# if defined(__cplusplus)
# define export(T) extern "C" T
# else
# define export(T) T
# endif
# endif
#endif

为了方便的使用这里使用WASM_ASYNC_COMPILATION=1参数返回Promise使用MODULARIZE=1参数生成js模块,并将输出文件改为main.mjs以输出es6风格的js文件,为方便使用,新建一个package.json来执行命令

package.json

1
2
3
4
5
6
7
{
"scripts": {
"build": "emcc -s WASM_ASYNC_COMPILATION=1 -s MODULARIZE=1 lib.c -o lib.mjs",
"dev": "node main.mjs"
}
}

使用wasm模块

1
2
3
4
5
6
7
8
import CModule from './lib.mjs'

const module = await CModule()

const sum = module._add(1, 2)

console.log(sum) // 3

与cmake一起打包C工程


C语言编译WebAssembly
https://qingshaner.com/C语言编译WebAssembly/
作者
清山
发布于
2023年4月20日
许可协议