### 在单独的文件用 Go 调用 C 代码
现在让我们继续讨论当 C 代码位于单独的文件中时如何从 Go 程序中调用 C 代码。
首先,我将解释用程序解决的假想的问题。然后我们需要使用已经实现好的两个 C 函数,这些函数我们不希望或无法在 Go 中重写。
#### C 代码部分
本小节将为你提供示例的 C 代码。它有两个文件:`callC.h`和`callC.c`。文件`(callC.h)`包含以下代码:
```c
#ifndef CALLC_H
#define CALLC_H
void cHello();
void printMessage(char* message);
#endif
```
文件`(callC.c)`包含以下代码:
```c
#include <stdio.h>
#include "callC.h"
void cHello() {
printf("Hello from C!\n");
}
void printMessage(char* message) {
printf("Go send me %s\n", message);
}
```
`callC.c`和`callC.h`文件都存储在单独的目录中,我们把该目录命名为`callClib`。但是,你可以使用你想使用的任何目录名称。
> Tip: 只要你使用正确的类型和数量的参数调用正确的 C 函数,那么的 C 代码的具体实现并不重要。C 代码中没有任何内容告诉我们它将在 Go 程序中使用。You should look at the Go code for the juicy part.
#### Go 代码部分
本小节将为你提供示例的 Go 源代码,该源代码名为`callC.go`,并将分三部分向你介绍。
第一部分的代码:
```Go
package main
// #cgo CFLAGS: -I${SRCDIR}/callClib
// #cgo LDFLAGS: ${SRCDIR}/callC.a
// #include <stdlib.h>
// #include <callC.h>
import "C"
```
整个 Go 源文件中最重要的 Go 语句是使用单独的 `import`语句包含 C 包。但是,`C`是一个虚拟的 Go 包,它只是告诉`go build`在 go 编译器处理文件之前使用 `cgo` 工具对其输入文件进行预处理。你仍然可以看到你需要使用注释来告知 Go 程序有关 C 代码的信息。在这种情况下,你告诉`callC.go`在哪里可以找到`callC.h`文件以及在哪里可以找到我们将在一段时间内创建的`callC.a`库文件,就像以`#cgo`开头的一段代码。
第一部分的代码:
```Go
import (
"fmt"
"unsafe"
)
func main() {
fmt.Println("Going to call a C function!")
C.cHello()
```
最后一部分代码:
```Go
fmt.Println("Going to call another C function!")
myMessage := C.CString("This is Mihalis!")
defer C.free(unsafe.Pointer(myMessage))
C.printMessage(myMessage)
fmt.Println("All perfectly done!")
}
```
为了从 Go 将字符串传递给 C 函数,你将需要使用`C.CString()`创建一个 C 字符串。此外,你将需要一个`defer`语句,以便在不再需要 C 字符串时释放其存储空间。 defer 语句包括对`C.free()`和`unsafe.Pointer()`的调用。
在下一部分中,你将看到如何编译和执行`callC.go`。
#### 混合 Go 和 C 代码
现在你已经有了 C 代码和 Go 代码,现在该学习下一步以执行调用 C 代码的 Go 文件了。
由于所有的关键信息都包含在 Go 文件中了,所以你唯一需要做的就是编译 C 代码以生成一个库:
```shell
$ ls -l callClib/
total 16
-rw-r--r--@ 1 mtsouk staff 162 Jan 10 09:17 callC.c
-rw-r--r--@ 1 mtsouk staff 89 Jan 10 09:17 callC.h
$ gcc -c callClib/*.c
$ ls -l callC.o
-rw-r--r-- 1 mtsouk staff 952 Jan 22 22:03 callC.o
$ file callC.o
callC.o: Mach-O 64-bit object x86_64
$ /usr/bin/ar rs callC.a *.o
ar: creating archive callC.a
$ ls -l callC.a
-rw-r--r-- 1 mtsouk staff 4024 Jan 22 22:03 callC.a
$ file callC.a
callC.a: current ar archive
$ rm callC.o
```
之后,你将在与 callC.go 文件相同的目录中拥有一个名为`callC.a`的文件。 gcc 可执行命令是**C 编译器**的名称。
现在,你可以使用 Go 代码编译文件创建一个新的可执行文件:
```shell
$ go build callC.go
$ ls -l callC
-rwxr-xr-x 1 mtsouk staff 2403184 Jan 22 22:10 callC
$ file callC
callC: Mach-O 64-bit executable x86_64
```
执行`callC`可执行文件,你将得到输出:
```shell
$ ./callC
Going to call a C function!
Hello from C!
Going to call another C function!
Go send me This is Mihalis!
All perfectly done!
```
> Tip: 如果要调用少量的 C 代码,强烈建议 C 和 Go 代码在单个 Go 文件中。但是,如果你要进行更复杂和更高级的操作,则首选静态 C 库。
- 介绍
- 1 Go与操作系统
- 01.1 Go的历史
- 01.2 Go的未来
- 01.3 Go的优点
- 01.3.1 Go是完美的么
- 01.3.2 什么是预处理器
- 01.3.3 godoc
- 01.4 编译Go代码
- 2 理解 Go 的内部构造
- Go 编译器
- Go 的垃圾回收
- 三色算法
- 有关 Go 垃圾收集器操作的更多信息
- Maps, silces 与 Go 垃圾回收器
- Unsafe code
- 有关 unsafe 包
- 另一个 usafe 包的例子
- 从 Go 调用 C 代码
- 在同一文件用 Go 调用 C 代码
- 在单独的文件用 Go 调用 C 代码
- 从 C 调用 Go 代码
- Go 包
- C 代码
- defer 关键字
- 用 defer 打印日志
- Panic 和 Recover
- 单独使用 Panic 函数
- 两个好用的 UNIX 工具
- strace
- dtrace
- 配置 Go 开发环境
- go env 命令
- Go 汇编器
- 节点树
- 进一步了解 Go 构建
- 创建 WebAssembly 代码
- 对 Webassembly 的简单介绍
- 为什么 WebAssembly 很重要
- Go 与 WebAssembly
- 示例
- 使用创建好的 WebAssembly 代码
- Go 编码风格建议
- 练习和相关链接
- 本章小结
- 3 Go基本数据类型
- 03.1 Go循环
- 03.1.1 for循环
- 03.1.2 while循环
- 03.1.3 range关键字
- 03.1.4 for循环代码示例
- 03.3 Go切片
- 03.3.1 切片基本操作
- 03.3.2 切片的扩容
- 03.3.3 字节切片
- 03.3.4 copy()函数
- 03.3.5 多维切片
- 03.3.6 使用切片的代码示例
- 03.3.7 使用sort.Slice()排序
- 03.4 Go 映射(map)
- 03.4.1 Map值为nil的坑
- 03.4.2 何时该使用Map?
- 03.5 Go 常量
- 03.5.1 常量生成器:iota
- 03.6 Go 指针
- 03.7 时间与日期的处理技巧
- 03.7.1 解析时间
- 03.7.2 解析时间的代码示例
- 03.7.3 解析日期
- 03.7.4 解析日期的代码示例
- 03.7.5 格式化时间与日期
- 03.8 延伸阅读
- 03.9 练习
- 03.10 本章小结
- 9 并发-Goroutines,Channel和Pipeline
- 09.1 关于进程,线程和Go协程
- 09.1.1 Go调度器
- 09.1.2 并发与并行
- 09.2 Goroutines
- 09.2.1 创建一个Goroutine
- 09.2.2 创建多个Goroutine
- 09.3 优雅地结束goroutines
- 09.3.1 当Add()和Done()的数量不匹配时会发生什么?
- 09.4 Channel(通道)
- 09.4.1 通道的写入
- 09.4.2 从通道接收数据
- 09.4.3 通道作为函数参数传递
- 09.5 管道
- 09.6 延展阅读
- 09.7 练习
- 09.8 本章小结