开始使用
Hello World
将一个 C 函数编译成 Go package,并在 Go 测试中调用它。维护者C2Go contributors本章会把一个 C 加法函数编译成 Go package,并在 Go 测试中调用它。开始前,请确认匹配版本的 SDK 已加入 PATH,并把 C2GO_TARGET 设置为下载平台包对应的 target triple。
1. 创建 Go module
mkdir hello-c2go
cd hello-c2go
mkdir .c2go
go mod init example.com/hello-c2go
go get github.com/c2gohq/c2go_libc@v0.20260809.0-rc.5
设置与 SDK 匹配的 triple。以 macOS arm64 为例:
export C2GO_TARGET=aarch64-apple-darwin
2. 编写 C 函数
创建 .c2go/input.c:
#include <stdint.h>
#include <c2go.h>
c2go_extern int add(int a, int b) {
return a + b;
}
c2go_extern 会把一个已经定义的 C 函数导出。默认 Go 名称会按下划线分段转换为 CamelCase:add 生成 Add,sqlite3_open 生成 Sqlite3Open。
C 的 int 在这些 64 位 target 上仍是 32 位,因此生成签名是 func Add(a, b int32) int32,不是 Go 的机器字宽 int。测试中的 20 和 22 是无类型常量,可以直接传入。
请把 C 源码和编译器中间产物放在隐藏的 .c2go/ 目录。未启用 cgo 时,Go command 会拒绝 package 目录中的普通 .c 文件;package discovery 会忽略点号开头的目录。
3. 使用 c2go-clang 编译
c2go-clang --target="$C2GO_TARGET" \
-fc2go \
-fc2go-package=example.com/hello-c2go/translated \
-O2 \
-fc2go-emit-plan9-asm=.c2go/translated.s \
-fc2go-emit-manifest=.c2go/translated.json \
.c2go/input.c
编译器会写入 .c2go/translated.s 与 .c2go/translated.json;这条 emit 路径不会产生可用的 translated.o 目标文件。
4. 生成 Go package
mkdir -p translated
c2go-bind \
--out=translated \
--sidecar=.c2go/translated.json \
.c2go/translated.s
输出目录现在包含 Go 声明、汇编、ABI guard、runtime 胶水和生成代码授权记录。再分发生成的 package 时,应保留这些授权文件。
5. 从 Go 调用
在 module 根目录创建 add_test.go:
package hello_c2go_test
import (
"testing"
"example.com/hello-c2go/translated"
)
func TestAdd(t *testing.T) {
got := translated.Add(20, 22)
if got != 42 {
t.Fatalf("translated.Add(20, 22) = %d, want 42", got)
}
}
运行测试:
go test ./...
预期结果:
ok example.com/hello-c2go
? example.com/hello-c2go/translated [no test files]
刚才发生了什么
编译器针对选定的 Go OS 与架构,把 C 降低成 Plan 9 汇编。manifest 告诉 c2go-bind:add 是导出函数、汇编使用哪个 target 和 contract epoch、以及需要哪些 runtime 支持。c2go-bind 生成 Go 可见的 Add 声明,并把匹配的汇编复制到 package 中。
下一章进入构建管线,理解 target、package path、汇编和 manifest 为什么必须保持一致。