Golang中使用cgo或c-package机制的自定义编译器组合实践

Golang中使用cgo或c-package机制的自定义编译器组合实践 我仍然是一个编程新手。 我打算结合使用我自己的编译器和Go语言。 因此,我想了解cgo是如何工作的。 我知道cgo很难理解,但如果你能提供一个关于C包如何工作的链接或解释,我将不胜感激。 如果你能告诉我,我将非常感激 (^ _ ^ )

7 回复

感谢您深入讲解了我对魔法导入概念感到模糊的地方 ^_^

更多关于Golang中使用cgo或c-package机制的自定义编译器组合实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是的,如果你仔细阅读,我的问题是“使用 #include 是否会减慢进程?文章中没有澄清这部分。我(?)我是一个容易……的人。”

顺便说一下,感谢你让我知道这个问题。 为了确认我的理解,我想请你检查一下相似之处。 也就是说,使用 import "C" 会运行 C/C++ 编译器来编译 Go 代码。因此,编译会花费很长时间。

import "C"

如果你还没有阅读过,我建议你阅读 cgo 命令文档: https://golang.org/cmd/cgo/ 以及 Golang 博客文章: https://blog.golang.org/cgo 不过,我也推荐 Dave Cheney 关于 cgo 的一些文章: https://dave.cheney.net/tag/cgo

fea_kafea:

使用 #include 会减慢进程吗?

使用 cgo 会减慢编译和执行速度。编译速度变慢是因为 Go 除了编译 Go 代码外,还必须运行 C/C++ 编译器来编译非 Go 代码;执行速度变慢是因为 Go 的运行时和调用约定与 C 不兼容,因此在 Go 和 C/C++ 之间进行调用会产生开销。

也就是说,使用 import "C" 会让 C/C++ 编译器来编译 Go 代码。

根据定义,只有 Go 编译器才能编译 Go 代码(也就是说,一个也能编译 Go 代码的 C/C++ 编译器,它本身就是一个 Go 编译器)。import "C" 在 Go 中是一个“魔法”导入,当 Go 编译器看到它时,Go 编译器会启动 C 编译器来编译 import "C" 上方的注释文本。我不清楚 Go 编译器具体是如何将这些注释文本提供给 C 编译器的;可能是将其复制到一个临时文件中,然后作为输入传递给 C 编译器。在 C 编译器编译完 C 代码之后,Go 编译器还需要在任何调用 C 函数的地方生成包装代码,因为 C 的调用约定和操作系统线程的使用方式与 Go 运行时执行 Go 函数和 goroutine 的方式不同且不兼容。然后,编译好的 Go 代码和 C 代码必须进行链接。根据一些谷歌搜索的结果,其中一次搜索将我带到了这里,看起来链接可能是一个耗时较多的步骤,但我不清楚具体细节。

我唯一使用过 cgo 的地方是这里,但我无法告诉你我为什么要这么做;我不记得了。无论当时是什么原因,我后来发现了如何在 Go 中不使用 cgo 来实现同样的功能,就不再使用这个了。

在Go中通过cgo调用C代码的基本方式如下:

// 文件:main.go
package main

// #include <stdio.h>
// #include <stdlib.h>
// 
// void myCFunction() {
//     printf("来自C函数的调用\n");
// }
import "C"
import "fmt"

func main() {
    fmt.Println("Go代码开始执行")
    C.myCFunction()  // 调用C函数
    fmt.Println("返回Go代码")
}

编译运行:

go run main.go

输出:

Go代码开始执行
来自C函数的调用
返回Go代码

更复杂的示例,包含参数传递和内存管理:

package main

/*
#include <stdlib.h>
#include <string.h>

char* processString(const char* input) {
    char* output = (char*)malloc(strlen(input) + 1);
    strcpy(output, input);
    // 转换为大写
    for(int i = 0; output[i]; i++) {
        if(output[i] >= 'a' && output[i] <= 'z')
            output[i] = output[i] - 32;
    }
    return output;
}
*/
import "C"
import (
    "fmt"
    "unsafe"
)

func main() {
    goStr := "hello, cgo!"
    
    // Go字符串转换为C字符串
    cStr := C.CString(goStr)
    defer C.free(unsafe.Pointer(cStr))  // 确保释放内存
    
    // 调用C函数
    result := C.processString(cStr)
    defer C.free(unsafe.Pointer(result))  // 释放C函数分配的内存
    
    // C字符串转换回Go字符串
    goResult := C.GoString(result)
    fmt.Printf("原字符串: %s\n", goStr)
    fmt.Printf("处理后: %s\n", goResult)
}

使用独立的C头文件和源文件:

// 文件:main.go
package main

// #cgo CFLAGS: -I.
// #include "mylib.h"
import "C"

func main() {
    C.sayHello()
    result := C.addNumbers(10, 20)
    println("结果:", int(result))
}
// 文件:mylib.h
#ifndef MYLIB_H
#define MYLIB_H

void sayHello();
int addNumbers(int a, int b);

#endif
// 文件:mylib.c
#include <stdio.h>
#include "mylib.h"

void sayHello() {
    printf("Hello from C library\n");
}

int addNumbers(int a, int b) {
    return a + b;
}

编译运行:

go build -o myapp main.go
./myapp

cgo的关键机制:

  1. import "C"语句必须紧跟在C代码注释之后
  2. C代码通过C.前缀访问
  3. 使用C.CString()C.GoString()进行字符串转换
  4. C分配的内存必须使用C.free()释放
  5. #cgo指令可以指定编译标志和链接选项

这种机制允许Go直接调用C函数,使你可以将自定义编译器生成的C代码集成到Go程序中。

回到顶部