Skip to content
On this page

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 限制

  1. 性能开销:CGO 调用有额外开销
  2. 垃圾回收:C 代码不会触发 GC
  3. 并发安全:需要注意线程安全
  4. 调试困难:跨语言调试复杂

实战示例

使用 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

进阶教程到此结束!

基于 MIT 许可发布