CGO
CGO 允许 Go 调用 C 代码,也允许 C 调用 Go 代码。本章将详细介绍 CGO 的使用。
基础 CGO
调用 C 函数
go
package main
/*
#include <stdio.h>
void hello() {
printf("Hello from C!\n");
}
*/
import "C"
func main() {
C.hello()
}
编译 CGO 程序
bash
# 编译
go build main.go
# 运行
./main
数据类型转换
基本类型
go
/*
int add(int a, int b) {
return a + b;
}
*/
import "C"
func main() {
a := C.int(10)
b := C.int(20)
result := C.add(a, b)
fmt.Println(result)
}
字符串转换
go
/*
#include <string.h>
void printString(const char* s) {
printf("%s\n", s);
}
*/
import "C"
func main() {
str := C.CString("Hello from Go")
defer C.free(unsafe.Pointer(str))
C.printString(str)
}
Go 字符串到 C 字符串
go
func goStringToCString(s string) *C.char {
return C.CString(s)
}
func cStringToGoString(s *C.char) string {
return C.GoString(s)
}
切片和数组
Go 切片到 C 数组
go
/*
void processArray(int* arr, int len) {
for (int i = 0; i < len; i++) {
printf("%d\n", arr[i]);
}
}
*/
import "C"
func main() {
nums := []int{1, 2, 3, 4, 5}
C.processArray(
(*C.int)(unsafe.Pointer(&nums[0])),
C.int(len(nums)),
)
}
C 数组到 Go 切片
go
/*
int* createArray(int len) {
int* arr = (int*)malloc(len * sizeof(int));
for (int i = 0; i < len; i++) {
arr[i] = i * 2;
}
return arr;
}
void freeArray(int* arr) {
free(arr);
}
*/
import "C"
func main() {
len := 5
cArr := C.createArray(C.int(len))
defer C.freeArray(cArr)
// 转换为 Go 切片
goSlice := (*[1 << 30]int)(unsafe.Pointer(cArr))[:len:len]
fmt.Println(goSlice)
}
结构体
Go 结构体到 C 结构体
go
/*
typedef struct {
int x;
int y;
} Point;
void printPoint(Point p) {
printf("Point: (%d, %d)\n", p.x, p.y);
}
*/
import "C"
func main() {
point := C.Point{
x: C.int(10),
y: C.int(20),
}
C.printPoint(point)
}
C 结构体到 Go 结构体
go
/*
typedef struct {
char name[50];
int age;
} Person;
Person* createPerson(const char* name, int age) {
Person* p = (Person*)malloc(sizeof(Person));
strcpy(p->name, name);
p->age = age;
return p;
}
void freePerson(Person* p) {
free(p);
}
*/
import "C"
func main() {
name := C.CString("张三")
defer C.free(unsafe.Pointer(name))
cPerson := C.createPerson(name, C.int(25))
defer C.freePerson(cPerson)
// 访问字段
fmt.Printf("姓名: %s\n", C.GoString(&cPerson.name[0]))
fmt.Printf("年龄: %d\n", cPerson.age)
}
回调函数
Go 函数作为 C 回调
go
/*
typedef void (*Callback)(int);
void registerCallback(Callback cb) {
cb(42);
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func callback(value int) {
fmt.Printf("回调: %d\n", value)
}
func main() {
cb := C.Callback(C.callback)
C.registerCallback(cb)
}
//export callback
func callback(value C.int) {
fmt.Printf("回调: %d\n", value)
}
错误处理
C 错误到 Go 错误
go
/*
int divide(int a, int b, int* result) {
if (b == 0) {
return -1;
}
*result = a / b;
return 0;
}
*/
import "C"
func divide(a, b int) (int, error) {
var result C.int
err := C.divide(C.int(a), C.int(b), &result)
if err != 0 {
return 0, fmt.Errorf("除法错误")
}
return int(result), nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("结果:", result)
}
内存管理
手动内存管理
go
/*
int* createInt(int value) {
int* p = (int*)malloc(sizeof(int));
*p = value;
return p;
}
void freeInt(int* p) {
free(p);
}
*/
import "C"
func main() {
cInt := C.createInt(C.int(42))
defer C.freeInt(unsafe.Pointer(cInt))
fmt.Println(*(*int)(unsafe.Pointer(cInt)))
}
CGO 最佳实践
1. 及时释放内存
go
func processCString(cStr *C.char) {
defer C.free(unsafe.Pointer(cStr))
goStr := C.GoString(cStr)
fmt.Println(goStr)
}
2. 减少跨边界调用
go
// 不推荐:频繁调用
func processItems(items []Item) {
for _, item := range items {
C.processItem(C.int(item.ID))
}
}
// 推荐:批量处理
func processItemsOptimized(items []Item) {
ids := make([]C.int, len(items))
for i, item := range items {
ids[i] = C.int(item.ID)
}
C.processItems(&ids[0], C.int(len(ids)))
}
3. 错误处理
go
func safeCall() error {
if C.someFunction() != 0 {
return fmt.Errorf("C 函数调用失败")
}
return nil
}
CGO 限制
- 性能开销:CGO 调用有额外开销
- 垃圾回收:C 代码不会触发 GC
- 并发安全:需要注意线程安全
- 调试困难:跨语言调试复杂
实战示例
使用 C 库
go
/*
#include <math.h>
*/
import "C"
func main() {
result := C.sin(C.double(1.0))
fmt.Println(result)
}
调用系统 API
go
/*
#include <unistd.h>
*/
import "C"
func main() {
C.usleep(C.useconds_t(1000000)) // 睡眠 1 秒
}
总结
- CGO 允许 Go 和 C 互操作
- 注意类型转换和内存管理
- 及时释放 C 分配的内存
- 减少跨边界调用
- 注意性能和安全性
- 谨慎使用,优先用纯 Go
进阶教程到此结束!