如何在 Go 代码中运行 C 语言代码

简介: 在前面多篇 Go 系列文章中,我们了解到,Go 语言脱胎于 C 语言,这就意味着在某些更底层的细节中,我们可以使用 C 语言实现,然后通过 Go 来调用相关的 C 代码。其实这一特点,在 Java 的 JVM、Python 的解释器也是通过底层是直接调用 C 实现的。而本篇文章就来学习一下,如何在 Go 语言中运行 C 程序。

前言

在前面多篇 Go 系列文章中,我们了解到,Go 语言脱胎于 C 语言,这就意味着在某些更底层的细节中,我们可以使用 C 语言实现,然后通过 Go 来调用相关的 C 代码。其实这一特点,在 Java 的 JVM、Python 的解释器也是通过底层是直接调用 C 实现的。


而本篇文章就来学习一下,如何在 Go 语言中运行 C 程序。


直接在 Go 代码中写入 C 程序

Go 语言通过 cgo 攻击来识别代码中的 C 语言,我们可以通过命令 go env 来查看是否 cgo 工具是否开启。

image.png

CGO_ENABLED=1 表示 cgo 工具可用,当设置为 0 时,表示工具不可用。


然后我可以新建一个 CinGo.go 的程序,然后在注释中写入 c 语言的代码。然后导入 Go 提供的 c 包 import "C" ,Go 语言在看到导入这个包之后就知道如何去处理注释中的内容了。

这里我们在 C 代码中写入要给 callC() 函数,然后在 Go 语言中进行调用:

package main
// #include <stdio.h>
// void callC() {
//   printf("Hello World from C!\n");
// }
import "C"
import "fmt"
func main() {
  fmt.Println("让我们学习 Go 语句调用 C 程序")
  C.callC()
  fmt.Println("调用 C 程序结束")
}

执行结果:

$ go run CinGo.go
让我们学习 Go 语句调用 C 程序
Hello World from C!
调用 C 程序结束

但是,这种方式的 C 代码和 Go 语言代码在同一个文件中,所以大家能明显发现这种方式的代码耦合度太高,仅仅适用于项目简单单一的情形。

一个更合理的方式应该是 C 代码单独在一个文件。


Go 直接调用 C 文件

那么,如果已经写好一个封装好的 C 文件代码,Go 语言该如何调用呢?

此时我们需要直接写好 C 代码,因为 Go 代码是无法对 C 代码文件进行重写或者修改的。

写好 C 头文件

我们在本地 Go 项目中,创建一个 hello.h 的头文件,代码如下:

#ifndef HELLO_H
#define HELLO_H
int sayHello(const char *name, char *out);
void printMessage(char *message);
void cHello();
int add(int a, int b);
#endif

编写 C 文件

然后编写 hello.c 文件,如下:

#include "hello.h"
#include <stdio.h>
int sayHello(const char *name, char *out) {
    int n;
    n = sprintf(out, "Hello, My name is %s!", name);
    return n;
}
void cHello() {
    printf("Hello from C!\n");
}
void printMessage(char* message) {
    printf("从 Go 语言接收的信息: %s\n", message);
}
int add(int a, int b) {
    return a + b;
}

写好 Go 代码

最后编写我们的 main.go 语言:

  • 我们需要在 CFLAGS 参数中填入我们的 GOPath 路径, #cgo CFLAGS: -I /Users/yuzhou_1su/go/src/CinGo
  • 然后在 LSFLAGS 中填入我们的 C 编译后的本地链接文件: #cgo LDFLAGS: /Users/yuzhou_1su/go/src/CinGo/hello.a
package main
// #cgo CFLAGS: -I /Users/yuzhou_1su/go/src/CinGo
// #cgo LDFLAGS: /Users/yuzhou_1su/go/src/CinGo/hello.a
// #include <stdlib.h>
// #include <hello.h>
import "C"
import (
    "fmt"
    "unsafe"
)
func main() {
    C.cHello()
    a := C.int(1024)
    b := C.int(2022)
    result := C.add(a, b)
    fmt.Println("Reuslt is:", result)
    goMessage := C.CString("This is Go")
    defer C.free(unsafe.Pointer(goMessage))
    C.printMessage(goMessage)
}

最后代码结构如下:


image.png


然后我们首先编译 c 代码:

$ gcc -c *.c      
$ /usr/bin/ar rs hello.a *.o
ar: creating archive hello.a
$ rm hello.o

然后再执行 Go 代码,结果如下:

$ go run main.go         
Hello from C!
Reuslt is: 3046
从 Go 语言接收的信息: This is Go

总结

在编写上述的小案例过程你中的,都出现了了很多小问题,比如 C 代码和 import "c"  语句之间不能有空格。经常会出现找不到 C 函数等等问题。

总得来说,日常 Go 开发还是不需要此类高级用法,也就是说其实我们平常编程过程中不太需要 cgo,大多数情况下还是尽量用 Go 语言自己实现。如果确实需要使用 C 语言,还是得多去了解 cgo 的文档,以防出错。

相关文章
|
5天前
|
人工智能 Go 调度
掌握Go并发:Go语言并发编程深度解析
掌握Go并发:Go语言并发编程深度解析
|
1天前
|
传感器 算法 C语言
C语言在嵌入式系统开发中的优化策略与代码实现
C语言在嵌入式系统开发中的优化策略与代码实现
10 1
|
2天前
|
存储 Go
用Go语言实现一个单协程消费者模型
用Go语言实现一个单协程消费者模型
12 0
|
2天前
|
编译器 Go C语言
一文速通Go语言面向对象编程
一文速通Go语言面向对象编程
9 0
|
2天前
|
Java 编译器 Go
一文速通go语言类型系统
一文速通go语言类型系统
7 0
|
2天前
|
存储 Java 编译器
一文快速掌握Go语言切片
一文快速掌握Go语言切片
4 0
|
2天前
|
存储 Java Linux
聊聊Go程序是如何运行的
聊聊Go程序是如何运行的
7 0
|
2天前
|
自然语言处理 安全 Java
速通Go语言编译过程
速通Go语言编译过程
14 0
|
4天前
|
Go 开发者
Golang深入浅出之-Go语言上下文(context)包:处理取消与超时
【4月更文挑战第23天】Go语言的`context`包提供`Context`接口用于处理任务取消、超时和截止日期。通过传递`Context`对象,开发者能轻松实现复杂控制流。本文解析`context`包特性,讨论常见问题和解决方案,并给出代码示例。关键点包括:1) 确保将`Context`传递给所有相关任务;2) 根据需求选择适当的`Context`创建函数;3) 定期检查`Done()`通道以响应取消请求。正确使用`context`包能提升Go程序的控制流管理效率。
14 1
|
5天前
|
安全 Go 开发者
Golang深入浅出之-Go语言并发编程面试:Goroutine简介与创建
【4月更文挑战第22天】Go语言的Goroutine是其并发模型的核心,是一种轻量级线程,能低成本创建和销毁,支持并发和并行执行。创建Goroutine使用`go`关键字,如`go sayHello(&quot;Alice&quot;)`。常见问题包括忘记使用`go`关键字、不正确处理通道同步和关闭、以及Goroutine泄漏。解决方法包括确保使用`go`启动函数、在发送完数据后关闭通道、设置Goroutine退出条件。理解并掌握这些能帮助开发者编写高效、安全的并发程序。
13 1