![ace84acbcf247d125a4356ebac261b44.png](https://i-blog.csdnimg.cn/blog_migrate/3e30f77f7fa7c160f3c4a7d6a1e635fc.jpeg)
过去的经验往往是走向未来的枷锁,因为在过时技术中投入的沉没成本会阻碍人们拥抱新技术。
——
chai2010
曾经一度因未能习得C++令人眼花缭乱的新标准而痛苦不已;Go语言“少即是多”的大道至简的理念让我重拾信心,寻回了久违的编程乐趣。
——Ending
C/C++经过几十年的发展,已经积累了庞大的软件资产,它们很多久经考验而且性能已经足够优化。Go语言必须能够站在C/C++这个巨人的肩膀之上,有了海量的C/C++软件资产兜底之后,我们才可以放心愉快地用Go语言编程。C语言作为一个通用语言,很多库会选择提供一个C兼容的API,然后用其他不同的编程语言实现。Go语言通过自带的一个叫CGO的工具来支持C语言函数调用,同时我们可以用Go语言导出C动态库接口给其他语言使用。
本章主要讨论CGO编程中涉及的一些问题。
2.1 快速入门
本节将通过一系列由浅入深的小例子来快速掌握CGO的基本用法。
2.1.1 最简CGO程序
真实的CGO程序一般都比较复杂,不过我们可以由浅入深。一个最简CGO程序该是什么样的呢?要构造一个最简CGO程序,首先要忽视一些复杂的CGO特性,同时要展示CGO程序和纯Go程序的差别来。下面是我们构建的最简CGO程序:
package main
import
"C"
func main()
{
println("hello cgo")
}
代码通过import "C"
语句启用CGO特性,主函数只是通过Go内置的println()
函数输出字符串,其中没有任何和CGO相关的代码。虽然没有调用CGO的相关函数,但是go build
命令会在编译和链接阶段启动gcc编译器,这已经是一个完整的CGO程序了。
2.1.2 基于C标准库函数输出字符串
前面那个CGO程序还不够简单,现在来看看更简单的版本:
package main
//#include <stdio.h>
import
"C"
func main()
{
C.puts(C.CString("Hello, Worldn"))
}
这个版本不仅通过import "C"
语句启用CGO特性,还包含C语言的<stdio.h>
头文件。然后通过cgo
包的C.CString()
函数将Go语言字符串转换为C语言字符串,最后调用cgo
包的C.puts()
函数向标准输出窗口打印转换后的C字符串。
与1.2节中的CGO程序的最大不同是:我们改用C.Cstring
来创建C语言字符串,而且改用puts()
函数直接向标准输出打印,之前是采用fputs
向标准输出打印。
没有释放使用C.CString
创建的C语言字符串会导致内存泄漏。但是对这个小程序来说,这样是没有问题的,因为程序退出后操作系统会自动回收程序的所有资源。
2.1.3 使用自己的C函数
前面使用了标准库中已有的函数。现在我们先自定义一个叫作SayHello
的C函数来实现打印,然后从Go语言环境中调用这个SayHello()
函数:
package main
/*
#include <stdio.h>
static void SayHello(const char* s) {
puts(s);
}
*/
import
"C"
func main()
{
C.SayHello(C.CString("Hello, Worldn"))
}
除SayHello()
函数是我们自己实现的之外,其他部分和前面的例子基本相似。
我们也可以将SayHello()
函数放到当前目录下的一个C语言源文件中(扩展名必须是.c
)。因为是编写在独立的C文件中,为了允许外部引用,所以需要去掉函数的static
修饰符。
// hello.c
#include
<stdio.h>
void
SayHello(const
char* s)
{
puts(s);
}
然后在CGO部分先声明SayHello()
函数,其他部分不变:
package main
//void SayHello(const char* s);
import
"C"
func main()
{
C.SayHello(C.CString("Hello, Worldn"))
}
既然SayHello()
函数已经放到独立的C文件中了,我们自然可以将对应的C文件编译打包为静态库或动态库文件供使用。如果是以静态库或动态库方式引用SayHello()
函数,需要将对应的C源文件移出当前目录