乐趣区

关于golang:cgo的几种使用方式

最简略的 CGO 程序

//cgo.go
package main

import "C"

func main(){println("hello cgo")
}

上述代码是一个残缺的 CGO 程序,通过 import “C” 语句启动了 CGO 个性,go build 命令会在编译和链接阶段启动 gcc 编译器

源码形式调用 C 函数

cgoTest.h

void SayHello(const char* s);

cgoTest.c

#include <stdio.h>
#include "cgoTest.h"

void SayHello(const char* s) {puts(s);
}

main.go

package main

/*
#include <cgoTest.h>
 */
import "C"

func main(){C.SayHello(C.CString("Hello world\n"))
}

上述.c 文件也能够是.cpp 文件,前提是编译时须要 g ++

cgoTest.cpp

#include <iostream>

extern "C" {#include "cgo01.h"}

void SayHello(const char* s) {std::cout << s;}

上述.c 和.cpp 的不同实现都实现了 SayHello 函数,阐明解放了函数的实现者,那如果是这种状况,可不可以应用 go 实现 SayHello 函数呢?

答案是能够的,这种技术也称为面向 C 语言接口 (.h 中的接口申明) 的编程技术,该技术不仅仅能够解放函数的实现者,同时也能够简化函数的使用者。

cgoTest.go

package main

import "C"

import "fmt"

//export SayHello
func SayHello(s *C.char){fmt.Print(C.GoString(s))  // 留神:这里是 C.GoString
}

留神:上述 main.go 文件在应用 C 函数 CString 后在程序退出前没有开释 C.CString 创立的字符串会导致内存透露,然而对于这个小程序来说,这样是没有问题的,因为程序推出后操作系统会主动回收程序的所有资源

改良后的 main.go 代码

package main

/*
#include <cgoTest.h>
#include <stdlib.h>
 */
import "C"
import "unsafe"

func main(){cs := C.CString("CPP Hello world\n")
    C.SayHello(cs)
    C.free(unsafe.Pointer(cs))
}

当然也有其余办法能够防止这种麻烦的状况呈现,而且只须要一个 go 文件就能够实现面向 C 语言的编程

main.go (只有这一个文件)

//+build go1.10
package main

//void SayHello(_GoString_ s); //Go1.10 中 CGO 新增的预约义 C 语言类型,用来示意 Go 语言字符串
import "C"
import "fmt"

//export SayHello
func SayHello(s string){ // 留神这里变量类型为 Go 中的 string
    fmt.Print(s)
}
func main(){C.SayHello("Hello CGO\n")
}

下面代码执行时先从 Go 语言的 main 函数开始,到 CGO 主动生成的 C 语言版本 SayHello 桥接函数,最初到 Go 语言环境的 SayHello 函数,是不是有一种合久必分、分久必合的感觉,这也是 CGO 编程的精髓所在。

外部机制

如果在一个 go 文件中呈现了 import “C” 指令则示意将调用 cgo 命令生成的对应的两头文件,下图是 cgo 生成的两头文件的示意图:

在保障 go build 没问题的状况下执行如下命令就能够生成两头文件

go tool cgo main.go

生成的两头文件在_obj 目录下

为了在 C 语言中应用 Go 语言定义的函数,咱们须要将 Go 代码编译为一个 C 动态库

go build -buildmode=c-archive -o SayHello.a  cgoTest.go

如果没有谬误的话,会生成一个 SayHello.a 动态库和 SayHello.h 头文件

既然提到了动态库的生成,顺便也说一下 Go 生成 C 动静库

go build -buildmode=c-shared -o SayHello.so cgoTest.go

编译和链接参数

编译和链接参数是每一个 C /C++ 程序员须要常常面对的问题。构建每一个 C /C++ 利用均须要通过编译和链接两个步骤,CGO 也是如此

编译参数:CFLAGS/CPPFLAGS/CXXFLAGS

编译参数次要是头文件的检索门路,预约义的宏等参数。实践上来说 C 和 C ++ 是齐全独立的两个编程语言,它们能够有着本人独立的编译参数。然而因为 C ++ 语言对 C 语言做了深度兼容,甚至能够将 C ++ 了解为 C 语言的超集,因而 C 和 C ++ 语言之间又会共享很多编译参数。因而 CGO 提供了 CFLAGS/CPPFLAGS/CXXFLAGS 三种参数,其中 CFLAGS 对应 C 语言编译参数(以.c 后缀名)、CPPFLAGS 对应 C /C++ 代码编译参数(.c,.cc,.cpp,.cxx)、CXXFLAGS 对应纯 C ++ 编译参数(.cc,.cpp,*.cxx)

链接参数:LDFLAGS

链接参数次要蕴含要链接库的检索目录和要链接库的名字。因为历史遗留问题,链接库不反对相对路径,咱们必须为链接库指定绝对路径。cgo 中的 ${SRCDIR} 为当前目录的绝对路径。通过编译后的 C 和 C ++ 指标文件格式是一样的,因而 LDFLAGS 对应 C /C++ 独特的链接参数

CGO 在应用 C /C++ 资源的时候个别有三种模式:间接应用源码;链接动态库;链接动静库。间接应用源码就是在 import “C” 之前的正文局部蕴含 C 代码,或者在以后包中蕴含 C /C++ 源文件。链接动态库和动静库的形式比拟相似,都是通过在 LDFLAGS 选项指定要链接的库形式链接

通过动态库的形式调用 C 函数

如果 CGO 中引入的 C /C++ 资源有代码而且代码规模也比拟小,间接应用源码是最现实的形式,但很多时候咱们并没有源代码,或者从 C /C++ 源代码开始构建的过程异样简单,这种时候应用 C 动态库也是一个不错的抉择。动态库因为是动态链接,最终的目标程序并不会产生额定的运行时依赖,也不会呈现动静库特有的跨运行时资源管理的谬误。不过动态库对链接阶段会有肯定要求:动态库个别蕴含了全副的代码,外面会有大量的符号,如果不同动态库之间呈现了符号抵触则会导致链接的失败

假如 dirname 下有 filename.c 文件和 filename.h 文件,则生成动态库的命令为

$ cd ./dirname
$ gcc -c -o filename.o filename.c
$ ar rcs libfilename.a filename.o

应用动态库中的 C 函数

package main

//#cgo CFLAGS: -I./dirname
//#cgo LDFLAGS: -L${SRCDIR}/dirname -lfilename
//
//#include "filename.h"
import "C"
import "fmt"

func main() {fmt.Println(C.filename_func())
}

通过动静库的形式调用 C 函数

动静库呈现的初衷是对于雷同的库,多个过程能够共享同一个,以节俭内存和磁盘资源。然而在磁盘和内存曾经白菜价的明天,这两个作用曾经显得微不足道了,那么除此之外动静库还有哪些存在的价值呢?从库开发角度来说,动静库能够隔离不同动静库之间的关系,缩小链接时呈现符号抵触的危险。而且对于 windows 等平台,动静库是逾越 VC 和 GCC 不同编译器平台的惟一的可行形式

动静库的生成

gcc -shared -o libfinename.so filename.c

对于 CGO 来说,应用动静库和动态库是一样的

package main

//#cgo CFLAGS: -I./dirname
//#cgo LDFLAGS: -L${SRCDIR}/dirname -lfilename
//
//#include "filename.h"
import "C"
import "fmt"

func main() {fmt.Println(C.filename_func())
}


退出移动版