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

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

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

在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 进行跨语言交互。

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

相关推荐

NUS邵林团队发布DexSinGrasp基于强化学习实现物体分离与抓取统一

本文的作者均来自新加坡国立大学LinSLab。本文的共同第一作者为新加坡国立大学实习生许立昕和博士生刘子轩,主要研究方向为机器人学习和灵巧操纵,其余作者分别为硕士生桂哲玮、实习生郭京翔、江泽宇以及...

「PLC进阶」如何通过编写SCL语言程序实现物料分拣?

01、前言SCL作为IEC61131-3编程语言的一种,由于其高级语言的特性,特别适合复杂运算、复杂数学函数应用的场合。本文以FactoryIO软件中的物料分拣案例作为硬件基础,介绍如何通过SCL来实...

zk源码—5.请求的处理过程一(http1.1请求方法)

大纲1.服务器的请求处理链...

自己动手从0开始实现一个分布式 RPC 框架

前言为什么要自己写一个RPC框架,我觉得从个人成长上说,如果一个程序员能清楚的了解RPC框架所具备的要素,掌握RPC框架中涉及的服务注册发现、负载均衡、序列化协议、RPC通信协议、Socket通信、异...

MLSys’25 | 极低内存消耗:用SGD的内存成本实现AdamW的优化性能

AIxiv专栏是机器之心发布学术、技术内容的栏目。过去数年,机器之心AIxiv专栏接收报道了2000多篇内容,覆盖全球各大高校与企业的顶级实验室,有效促进了学术交流与传播。如果您有优秀的工作想要分享,...

线程池误用导致系统假死(线程池会自动销毁吗)

背景介绍在项目中,为了提高系统性能使用了RxJava实现异步方案,其中异步线程池是自建的。但是当QPS稍微增大之后却发现系统假死、无响应和返回,调用方出现大量超时现象。但是通过监控发现,系统线程数正常...

大型乘用车工厂布局规划(六大乘用车基地)

乘用车工厂的布局规划直接影响生产效率、物流成本、安全性和未来扩展能力。合理的布局应确保生产流程顺畅、物流高效、资源优化,并符合现代化智能制造和绿色工厂的要求。以下是详细的工厂布局规划要点:1.工厂布...

西门子 S7-200 SMART PLC 连接Factory IO的方法

有很多同学不清楚如何西门子200smart如何连接FactoryIO,本教程为您提供了如何使用西门子S7-200SMARTPLC连接FactoryIO的说明。设置PC和PLC之间的...

西门子博图高级仿真软件的应用(西门子博途软件仿真)

1.博图高级仿真软件(S7-PLCSIMAdvancedV2.0)S7-PLCSIMAdvancedV2.0包含大量仿真功能,通过创建虚拟控制器对S7-1500和ET200SP控制器进行仿真...

PLC编程必踩的6大坑——请对号入座,评论区见

一、缺乏整体规划:面条式代码问题实例:某快递分拣线项目初期未做流程图设计,工程师直接开始编写传送带控制程序。后期增加质检模块时发现I/O地址冲突,电机启停逻辑与传感器信号出现3处死循环,导致项目延期2...

统信UOS无需开发者模式安装软件包
统信UOS无需开发者模式安装软件包

原文链接:统信UOS无需开发者模式安装软件包...

2025-05-05 14:55 csdh11

100个Java工具类之76:数据指纹DigestUtils

为了提高数据安全性,保证数据的完整性和真实性,DigestUtils应运而生。正确恰当地使用DigestUtils的加密算法,可以实现数据的脱敏,防止数据泄露或篡改。...

麒麟KYLINIOS软件仓库搭建02-软件仓库添加新的软件包

#秋日生活打卡季#原文链接:...

Java常用工具类技术文档(java中工具类的作用)

一、概述Java工具类(UtilityClasses)是封装了通用功能的静态方法集合,能够简化代码、提高开发效率。本文整理Java原生及常用第三方库(如ApacheCommons、GoogleG...

软路由的用法(自动追剧配置)(软路由教学)

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:值友98958248861环境和需求...