百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

小白都能看得懂的Cgo入门教程(cgo2.0教程)

csdh11 2025-03-14 15:57 1 浏览

在Go语言开发过程中,尽管Go本身功能强大,但仍然有许多C语言库可以复用,如操作系统 API、高性能计算库、数据库驱动等。Go 提供了一种强大的机制 —— Cgo,让我们可以在 Go 代码中调用 C 代码,或者与 C 语言库交互。

本文将从Cgo的基本语法入手,逐步讲解如何在Go代码中使用C语言函数、结构体、指针,以及如何在 C 代码中调用 Go 代码,最终实现与C共享库的集成。

Cgo 基础语法

Cgo 使用 import "C" 关键字来引入 C 代码,并在 Go 代码中调用 C 语言函数。

第一个 Cgo 示例

以下是一个简单的示例,展示了如何在 Go 代码中调用 C 代码:

package main

/*
#include 
#include 

void say_hello() {
    printf("Hello from C!\n");
}
*/
import "C"

func main() {
    C.say_hello()
}

代码解析:

  • import "C":告诉 Go 编译器,这里要使用 C 代码。
  • /* ... */:多行注释中的部分是 C 代码。
  • C.say_hello():在 Go 代码中调用 C 函数。

运行代码:

go run main.go

代码输出结果:

Hello from C!

Go 调用 C 标准库函数

Cgo 允许直接调用 C 标准库,如 malloc 和 free,实现手动内存管理。

使用 C 语言的 malloc 和 free

package main

/*
#include 
*/
import "C"
import "unsafe"

func main() {
    ptr := C.malloc(C.size_t(100)) // 分配 100 字节
    defer C.free(ptr)              // 释放内存
}

关键点:

  • C.malloc(C.size_t(100)):调用 C 语言的 malloc 申请 100 字节的内存。
  • C.free(ptr):使用 free 释放内存,避免内存泄漏。

Go传递数据给C代码

传递字符串,Go 语言的 string 不能直接传递给 C 代码,需要转换为 char* 类型。

package main

/*
#include 
#include 

void print_message(const char* msg) {
    printf("%s\n", msg);
}
*/
import "C"
import "unsafe"

func main() {
    msg := C.CString("Hello from Go!")   // 将 Go 字符串转换为 C 字符串
    defer C.free(unsafe.Pointer(msg))   // 释放 C 分配的内存
                                        // unsafe.Pointer:Go 和 C 指针转换需要 unsafe 包。
    C.print_message(msg)                // 调用 C 函数
}

传递结构体,C语言的结构体在Go代码中可以直接使用。

package main

/*
#include 

typedef struct {
    int id;
    char name[20];
} User;

void print_user(User u) {
    printf("User ID: %d, Name: %s\n", u.id, u.name);
}
*/
import "C"
import "unsafe"

func main() {
    user := C.User{id: 1}
    name := "Alice"
    copy((*[20]byte)(unsafe.Pointer(&user.name))[:], name) // 复制字符串到 C 结构体
    C.print_user(user)
}

说明:

  • C.User{id: 1}:创建一个 C 结构体。
  • copy((*[20]byte)(unsafe.Pointer(&user.name))[:], name):将 Go 字符串转换为 C 结构体中的 char name[20]。

C 代码调用 Go 代码

Cgo 允许 C 代码调用 Go 代码,但 Go 函数需要用 //export 关键字导出。

C 调用 Go示例:

package main

/*
#include 

extern void go_callback();
static void call_go() {  // 添加 static 关键字
                         // 非 static 的 call_go 会被 Go 代码和 Cgo 编译为 多个相同的符号,导致链接错误。
                        // 加上 static 后,call_go 只在当前 C 文件作用域可见,不会影响链接过程。
    go_callback();
}
*/
import "C"
import "fmt"

//export go_callback
func go_callback() {
    fmt.Println("Callback from C!")
}

func main() {
    C.call_go()
}

关键点:

  • //export go_callback:让 Go 函数暴露给 C 代码调用。
  • 只能用于 main 包。

使用 C 共享库

在实际项目中,我们可能会使用 C 编写的共享库 (.so 或 .dll),然后在 Go 代码中调用它。

创建C共享库,首先,编写两个C语言文件hello.c和hello.h:

#include 

void say_hello() {
    printf("Hello from shared library!\n");
}
// hello.h
#ifndef HELLO_H
#define HELLO_H

void say_hello();

#endif

然后编译生成共享库(我的机器是mac M3芯片):

gcc -shared -o libhello.so -fPIC -arch arm64 hello.c 

在 Go 代码中使用共享库

package main

/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -lhello
#include "hello.h"
*/
import "C"

func main() {
    C.say_hello()
}

编译go代码

go build -o main main.go

macOS 运行 go build 后,默认不会在当前目录查找 libhello.so,我们需要手动修改二进制文件的动态库加载路径:

install_name_tool -add_rpath @executable_path/. main

运行编译过后的程序,结果如下所示:

./main
Hello from shared library!

Cgo 的性能影响

Cgo 调用 C 代码时,Go 需要进行跨语言调用,导致性能开销较大:

  • 函数调用开销:每次调用 C 函数都会有额外的调用栈切换。
  • GC 兼容性:C 代码中的内存不会被 Go 的垃圾回收管理,可能导致内存泄漏。

优化建议

  • 减少 Cgo 调用次数,在Go代码中批量处理数据。
  • 避免在高频执行的代码中使用Cgo,如性能关键路径。
  • 优先使用纯Go方案,除非 C 代码提供了 Go 无法替代的功能。

总结

知识点

说明

import "C"

让 Go 代码能够调用 C 代码

C.CString

Go 字符串转换为 C 字符串

unsafe.Pointer

Go 指针与 C 指针转换

//export

让 C 代码调用 Go 代码

#cgo LDFLAGS

连接外部 C 共享库

适用场景:

需要调用现有的 C 语言库

需要与 C 代码进行交互

需要优化特定的高性能计算模块

不适用场景:

需要频繁调用 Cgo(开销大)

仅仅为了优化性能(Go 原生优化更有效)

Cgo是Go语言中强大的工具,但也带来了额外的复杂性和开销。在使用时,建议优先考虑纯Go方案,只有在需要时才使用 Cgo 进行跨语言交互。

由于本人知识有限,可能有些得不足之处是难免的事情,望谅解,如果你觉得这篇教程有帮助,欢迎 点赞收藏,并分享给你的朋友!今天的内容到此结束,感谢您的收看!!!

相关推荐

PromptDA:4K分辨率精准深度估计!(分辨率4k是多少p)

这里是FoxFeed,一个专注于科技的内容平台。背景介绍在计算机视觉领域,深度估计一直是一个重要的研究方向。近日,由DepthAnything团队开发的...

m4a怎么转换成mp3?教你这样转换音频格式

m4a怎么转换成mp3?M4A是MPEG-4音频标准的文件的扩展名,它可以存储各种类型的音频内容,运用比较广泛,尽管m4a被很多媒体应用兼容,但仍有很多应用无法打开它,将m4a转换成mp3就是一个很不...

“讲述初心故事 传递使命情怀”2019第五届江苏医院微电影节启动

“讲述初心故事传递使命情怀”,2019第五届江苏医院微电影节9月16日启动。江苏医院微电影节由新华网江苏有限公司和江苏省医院协会联合举办,扬子江药业集团协办,秉承“讲述初心故事传递使命情怀”为活动...

短视频宝贝=慢?阿里巴巴工程师这样秒开短视频

前言随着短视频兴起,各大APP中短视频随处可见,feeds流、详情页等等。怎样让用户有一个好的视频观看体验显得越来越重要了。大部分feeds里面滑动观看视频的时候,有明显的等待感,体验不是很好。针对这...

阿里巴巴工程师这样秒开短视频(阿里巴巴的工程师多少钱一个月)

前言随着短视频兴起,各大APP中短视频随处可见,feeds流、详情页等等。怎样让用户有一个好的视频观看体验显得越来越重要了。大部分feeds里面滑动观看视频的时候,有明显的等待感,体验不是很好。针对这...

旗鱼浏览器1.0 RC正式版候选版:增账户同步等

从9月19日发布第一个Beta版至今,约80天的时间便这么飞走了,作为2015年底的一个答卷,今天旗鱼浏览器1.0RC(正式版候选版)发布,如果没有意外,明天我们将发布电脑版和安卓版的第一个1.0正...

5种方法,教你将m3u8转换为mp4格式

m3u8格式在许播放器中不受支持,只能在浏览器中进行在线观看,然而,在线观看可能会不大方便,如果网络卡顿的话就会影响观感。想要将...

kgma格式怎么转换为mp3?试试这5种简单的音频转换方法!

由于kgma格式的特殊性和平台限制,除了专属的音乐平台外,其他设备和网络平台是无法识别或播放kgma格式的音乐的,因此为了方便使用,我们就必须将kgma格式转换为mp3。接下来,小编就为大家推荐5种简...

500+本程序员值得看的书籍,7大类,1大合集,收藏,日后有用

一、Golang书籍推荐入门《Go入门指南》...

教你编写最简单的CM3操作系统,160行实现任务创建与切换

如题,任务创建与上下文切换是跟硬件息息相关的,而这恰恰是RTOS编写的最难点,抛开这些功能,剩下的就是双向链表增删改操作了,本例用最精简的方式实现了任务创建与切换,OS启动等功能,并运用了Cortex...

Hot 3D 人体姿态估计 HPE Demo复现过程

视频讲解...

各编程语言相互调用示例,代码简单,生成的软件体积也很小

aardio支持混入很多不同的编程语言,代码简单,生成的软件体积也很小。下面看示例。...

你知道shell脚本中$0 $1 $# $@ $* $? $$ 都是什么意思吗?

一、概述shell中有两类字符:普通字符、元字符。1.普通字符...

NDK打印调用堆栈(logger.error打印堆栈信息)

虽然android源码里有android::CallStack用来打印堆栈,但是NDK里面并没有包含它,所以不能直接调用它,所以要尝试用动态调用的方式来实现。我测试的手机是安卓8.1.0版本,...

小白都能看得懂的Cgo入门教程(cgo2.0教程)

在Go语言开发过程中,尽管Go本身功能强大,但仍然有许多C语言库可以复用,如操作系统API、高性能计算库、数据库驱动等。Go提供了一种强大的机制——Cgo,让我们可以在Go代码中调用C...