ffi_build
SKILL.md
仓颉C互操作配置构建指导
0. 各平台库文件命名对照
| 平台 | 动态库 | 静态库 |
|---|---|---|
| Linux | lib<name>.so |
lib<name>.a |
| macOS | lib<name>.dylib |
lib<name>.a |
| Windows | lib<name>.dll |
lib<name>.lib |
注意:Windows 平台上依赖动态库,目前 cjc 和 cjpm 实现有差异:编译时,cjc 对动态库是否有
lib前缀不敏感,cjpm 则要求动态库必须有lib前缀。运行时,cjc 编出的可执行文件,依赖的动态库必须加上lib前缀,而 cjpm 构建出的可执行文件,依赖的动态库一定不能有lib前缀。请务必熟悉这个规则(实际是规格问题,未来会修复),保证能正常构建和运行。
1. cjc 编译器链接选项
| 选项 | 说明 |
|---|---|
-L <path> / --library-path <path> |
指定库文件搜索目录(优先级高于 LIBRARY_PATH 环境变量) |
-l <name> / --library <name> |
链接库文件,格式为 lib[name].[extension](如 -l draw 链接 libdraw.so/.a/.dll) |
2. 使用 cjc 直接编译
2.1 Linux
# 编译 C 代码为动态库
clang -shared -fPIC -fstack-protector-all native.c -o libnative.so
# 编译 C 代码为静态库
clang -c -fstack-protector-all native.c -o native.o && ar rcs libnative.a native.o
# 编译仓颉代码并链接
cjc -L . -l native main.cj -o main
# 运行(动态库须确保在搜索路径上)
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
./main
2.2 macOS
# 编译 C 代码为动态库
clang -shared -fPIC -fstack-protector-all native.c -o libnative.dylib
# 编译 C 代码为静态库
clang -c -fstack-protector-all native.c -o native.o && ar rcs libnative.a native.o
# 编译仓颉代码并链接
cjc -L . -l native main.cj -o main
# 运行(动态库须确保在搜索路径上)
export DYLD_LIBRARY_PATH=.:$DYLD_LIBRARY_PATH
./main
2.3 Windows
# 编译 C 代码为动态库(导出函数需加 __declspec(dllexport) 修饰)
clang -shared -fstack-protector-all native.c -o libnative.dll
# 编译仓颉代码并链接
cjc -L . -l native main.cj -o main.exe
# 运行(动态库须确保在搜索路径上)
./main.exe
注意:Windows 平台上 C 函数需使用
__declspec(dllexport)修饰以导出符号。
2.4 安全编译建议
编译 C 代码时应启用栈保护选项,防止缓冲区溢出攻击:
# 推荐选项(二选一)
clang -fstack-protector-all ... # 全部函数开启栈保护
clang -fstack-protector-strong ... # 对含局部数组/地址操作的函数开启栈保护
3. 基于 cjpm 项目编译(推荐)
3.1 项目初始化
cjpm init # 初始化项目
3.2 cjpm.toml 配置
在 cjpm.toml 中通过 [ffi.c] 节配置 C 库依赖:
[package]
name = "myproject"
cjc-version = "1.0.5"
version = "1.0.0"
output-type = "executable"
[ffi.c]
native = { path = "./libs/" } # path 是 C 动态/静态库所在路径
3.3 构建与运行
cjpm build # 构建时自动链接 [ffi.c] 设置的 C 库
cjpm run # 构建并运行
3.4 其他相关 cjpm.toml 配置项
| 配置项 | 说明 | 示例 |
|---|---|---|
output-type |
输出类型 | "executable" / "static" / "dynamic" |
compile-option |
传给 cjc 的额外编译选项 |
"-O1" |
link-option |
传给链接器的选项 | "-z noexecstack -z relro -z now" |
3.5 不同平台的 cjpm.toml 配置
当需要区分不同目标平台的库路径时,可使用 [target] 节:
Linux x86_64:
[ffi.c]
native = { path = "./libs/linux_x86_64/" }
macOS aarch64:
[ffi.c]
native = { path = "./libs/macos_aarch64/" }
Windows x86_64:
[ffi.c]
native = { path = ".\\libs\\windows_x86_64\\" }
3.6 链接安全加固选项
对于需要安全加固的项目,建议在 link-option 中添加链接器安全标志:
[package]
link-option = "-z noexecstack -z relro -z now"
| 选项 | 说明 |
|---|---|
-z noexecstack |
禁止栈上执行代码,防止栈溢出攻击 |
-z relro |
设置 GOT 为只读,防止 GOT 覆写攻击 |
-z now |
立即绑定所有符号,配合 relro 使用 |
4. 完整示例
4.1 Linux 完整编译流程
# 1. 编写 C 代码 (native.c)
# 2. 编译 C 代码为动态库
clang -shared -fPIC -fstack-protector-all native.c -o libnative.so
# 3. 使用 cjc 直接编译
cjc -L . -l native main.cj -o main
./main
# 或使用 cjpm 项目编译
# 将 libnative.so 放到 libs/ 目录下
# 在 cjpm.toml 中配置 [ffi.c] native = { path = "./libs/" }
cjpm build && cjpm run
4.2 Windows 完整编译流程
# 1. 编写 C 代码 (native.c),导出函数加 __declspec(dllexport) 修饰
# 2. 编译 C 代码为动态库
clang -shared -fstack-protector-all native.c -o libnative.dll
# 3.1 使用 cjc 编译
cjc -L . -l native main.cj -o main.exe
./main.exe
# 3.2 基于 cjpm 项目构建
# 将 libnative.dll 放到 libs/ 目录下(用于编译链接)
# 基于前面提到的运行问题,libs/ 下还应放一个名为 native.dll 的副本
# 在 cjpm.toml 中配置 [ffi.c] native = { path = "./libs/" }
# 构建,依赖 libnative.dll
cjpm build
# 运行,依赖 native.dll
cjpm run