C/C++经过几十年的发展,已经积累了庞大的软件资产,它们很多久经考验而且性能已经足够优化。Go语言必须能够站在C/C++这个巨人的肩膀之上,有了海量的C/C++软件资产兜底之后,我们才可以放心愉快地用Go语言编程。C语言作为一个通用语言,很多库会选择提供一个C兼容的API,然后用其他不同的编程语言实现。Go语言通过自带的一个叫CGO
的工具来支持C语言函数调用,同时我们可以用Go语言导出C动态库接口给其它语言使用。
如果有纯Go的解决方法就不要使用CGO;CGO中涉及的C和C++构建问题非常繁琐;CGO有一定的限制无法实现解决全部的问题;不要试图越过CGO的一些限制。而且CGO只是一种官方提供并推荐的Go语言和C/C++交互的方法。如果是使用的gccgo的版本,可以通过gccgo的方式实现Go和C/C++的交互。同时SWIG也是一种选择,并对C诸多特性提供了支持。
要使用CGO特性,需要安装C/C++构建工具链,在macOS和Linux下是要安装GCC
,在windows下是需要安装MinGW
工具。同时需要保证环境变量CGO_ENABLED
被设置为1,这表示CGO是被启用的状态。在本地构建时CGO_ENABLED默认是启用的,当交叉构建时CGO默认是禁止的。比如要交叉构建ARM环境运行的Go程序,需要手工设置好C/C++交叉构建的工具链,同时开启CGO_ENABLED环境变量。
本文参考: Go语言高级编程
基础概念 import "C"语句如果在Go代码中出现了import "C"
语句则表示使用了CGO特性,紧跟在这行语句前面的注释是一种特殊语法,里面包含的是正常的C语言代码。当确保CGO启用的情况下,还可以在当前目录中包含C/C++对应的源文件。
举个最简单的例子:
package main
/*
#include
void printint(int v) {
printf("printint: %d\n", v);
}
*/
import "C"
import "unsafe"
func main() {
v := 42
C.printint(C.int(v))
}
这个例子展示了cgo的基本使用方法。开头的注释中写了要调用的C函数和相关的头文件,头文件被include之后里面的所有的C语言元素都会被加入到”C”这个虚拟的包中。需要注意的是,import "C"导入语句需要单独一行,不能与其他包一同import。向C函数传递参数也很简单,就直接转化成对应C语言类型传递就可以。如上例中C.int(v)用于将一个Go中的int类型值强制类型转换转化为C语言中的int类型值,然后调用C语言定义的printint函数进行打印。
需要注意的是,Go是强类型语言,所以cgo中传递的参数类型必须与声明的类型完全一致,而且传递前必须用”C”中的转化函数转换成对应的C类型,不能直接传入Go中类型的变量。
在import "C"
语句前的注释中可以通过#cgo
语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。
// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include
import "C"
上面的代码中,CFLAGS
部分,-D
部分定义了宏PNG_DEBUG,值为1;-I
定义了头文件包含的检索目录。LDFLAGS
部分,-L
指定了链接时库文件检索目录,-l
指定了链接时需要链接png库。
因为C/C++遗留的问题,C头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径。在库文件的检索目录中可以通过${SRCDIR}变量表示当前包目录的绝对路径:
// #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo
上面的代码在链接时将被展开为:
项目示例 需求// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo
Go项目中需要调用C语言动态库libcoreserver.so
中的GetServerIP
方法获取服务器IP地址。
头文件CoreServer.h
内容如下:
#ifndef CoreServer__h
#define CoreServer__h
#define MASTER_INNER 0
#define MASTER_OUTTER 1
#define SLAVER_INNER 2
#define SLAVER_OUTER 3
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief GetServerIP 获取服务器IP
* @param sType 服务器类型取值范围0~3 例:MASTER_INNER
* @param sIP 返回获取的服务器ip
* @return 1 成功 -1 失败
*/
int GetServerIP(int sType,char* sIP);
#ifdef __cplusplus
}
#endif
#endif
Go项目文件结构
在项目根目录下创建include
文件夹,放置头文件CoreServer.h
,创建lib
文件夹,放置libcoreserver.so
通过#cgo
语句设置编译阶段和链接阶段的相关参数
/*
#cgo CFLAGS : -I../include
#cgo LDFLAGS: -L../lib -lcoreserver -lm
#include "CoreServer.h"
*/
import "C"
CFLAGS
部分,-I
定义了头文件包含的检索目录。LDFLAGS
部分,-
L指定了链接时库文件检索目录,-l
指定了链接时需要链接coreserver
库。
因为C/C++遗留的问题,C头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径。当前项目未确定库文件的路径,先暂时使用相对目录。运行时,会提示找不到库文件。
GOROOT=/home/kylin/go #gosetup
GOPATH=/home/kylin/go-space #gosetup
/home/kylin/go/bin/go build -o /tmp/___1go_build_main_go /home/kylin/go-space/src/insight-client/main.go #gosetup
/tmp/___1go_build_main_go
/tmp/___1go_build_main_go: error while loading shared libraries: libcoreserver.so: cannot open shared object file: No such file or directory
Process finished with the exit code 127
需要注意的是,在运行时需要将动态库放到系统能够找到的位置。对于windows来说,可以将动态库和可执行程序放到同一个目录,或者将动态库所在的目录绝对路径添加到PATH环境变量中。对于macOS来说,需要设置DYLD_LIBRARY_PATH环境变量。而对于Linux系统来说,需要设置LD_LIBRARY_PATH环境变量。
在默认情况下,编译器只会使用/lib和/usr/lib这两个目录下的库文件,如果放在其他路径也可以,需要让编译器知道库文件在哪里。
方法1:
编辑/etc/ld.so.conf文件,在新的一行中加入库文件所在目录;
运行ldconfig,以更新/etc/ld.so.cache文件;
方法2:
在/etc/ld.so.conf.d/目录下新建任何以.conf为后缀的文件,在该文件中加入库文件所在的目录;
运行ldconfig,以更新/etc/ld.so.cache文件;
第二种办法更为方便,对于原系统的改动最小。因为/etc/ld.so.conf文件的内容是include /etc/ld.so.conf.d/*.conf
所以,在/etc/ld.so.conf.d/目录下加入的任何以.conf为后缀的文件都能被识别到。
本文修改的/etc/ld.so.conf.d路径下的libc.conf文件
/etc/ld.so.conf.d$ sudo vim libc.conf
# libc default configuration
/usr/local/lib
/home/kylin/go-space/src/insight-client/lib
重点!!!!最后将修改写入缓存!!
sudo ldconfig
方法调用
在Go中调用C的GetServerIP
方法,获取主服务器IP地址。重点难点在于Go语言和C语言的数据类型转换。
func getInsightServerConfigure() (bool, string, error) {
bt := make([]byte, 64)
masterIp := (*C.char)(unsafe.Pointer(&bt[0]))
//获取主服务器IP
//#define MASTER_INNER 0
//#define MASTER_OUTTER 1
//#define SLAVER_INNER 2
//#define SLAVER_OUTER 3
serverConfigureType, err := config.Int("insight-server-configure-type")
if err != nil {
logs.Warning("获取使用平台配置的服务器地址的类型配置(insight-server-configure-type)失败,默认使用MASTER_INNER主服务器内网IP,为0", err)
serverConfigureType = 0
}
configureType := C.int(serverConfigureType)
r := C.GetServerIP(configureType, masterIp)
if r == 1 {
ip := GetStringByByte(bt)
logs.Info("获取的主服务器ip为:", ip)
if len(strings.Split(ip, ".")) == 4 {
InsightServerIp = ip
//设置insight核心服务IP地址配置
config.Set("insight-server-ip", InsightServerIp)
//全局设置insight核心服务地址
InsightServerAddress = "http://" + InsightServerIp + ":" + InsightServerPort + InsightServerContextPath
logs.Info("insight-server服务地址为:", InsightServerAddress)
return true, "", nil
} else {
err := errors.New("获取配置的服务器IP地址不正确")
return false, "获取配置的服务器IP地址不正确", err
}
} else {
err := errors.New("获取配置的服务器IP地址失败")
return false, "获取配置的服务器IP地址失败", err
}
}
交叉编译
说明: 以下的交叉编译主机是在 x86_64 Ubuntu 16.04 平台下进行的.
参考文章
Golang 交叉编译
arm-linux交叉编译环境的建立
go支持交叉编译,CGO本身不支持交叉编译,需要使用交叉编译工具
Go 交叉编译涉及的编译参数:
GOARCH
, 目标平台的 CPU 架构. 常用的值amd64
, arm64
,i386
,armhf
GOOS
, 目标平台, 常用的值 linux
, windows
, drawin
(macOS)
GOARM
, 只有 GOARCH
是 arm64
才有效, 表示 arm 的版本, 只能是 5, 6, 7 其中之一
CGO_ENABLED
, 是否支持 CGO
交叉汇编, 值只能是 0, 1, 默认情况下是 0, 启用交叉汇编比较麻烦
CC
, 当支持交叉汇编时(即 CGO_ENABLED=1), 编译目标文件使用的c 编译器
.
CXX
, 当支持交叉汇编时(即 CGO_ENABLED=1), 编译目标文件使用的 c++ 编译器
.
AR
, 当支持交叉汇编时(即 CGO_ENABLED=1), 编译目标文件使用的创建库文件命令.
GOOS=linux GOARCH=arm64 GOARM=7 CGO_ENABLED=1 CC=aarch64-linux-gnu-gcc AR=aarch64-linux-gnu-ar go build
交叉汇编 linux 系统 armhf 架构的目标文件
GOOS=linux GOARCH=arm CGO_ENABLED=1 CC=arm-linux-gnueabihf-gcc AR=arm-linux-gnueabihf-ar go build
arm 交叉汇编工具 (Ubuntu下)
tool | armhf | arm64 | eabi |
---|---|---|---|
gcc | gcc-arm-linux-gnueabihf | gcc-aarch64-linux-gnu | gcc-arm-linux-gnueabi |
g++ | g++-arm-linux-gnueabihf | g++-aarch64-linux-gnu | g++-arm-linux-gnueabi |
Ubuntu16.04下,与部署平台版本一致,直接使用apt-get
进行安装
sudo apt-get install gcc-arm-linux-gnueabihf
sudo apt-get install gcc-aarch64-linux-gnu
手动下载安装(手动安装在系统版本不一致的情况下会提示缺少依赖,很难安装成功)
编译常见错误 错误一arm 交叉汇编下载地址:
http://releases.linaro.org/components/toolchain/binaries, 选择
aarch64-linux-gnu arm-linux-gnueabi, arm-linux-gnueabihf
目录下的文件作为交叉编译工具.
交叉编译时,报错:*.so: file not recognized: 不可识别的文件格式
交叉编译arm,引入的c动态库so文件也必须是arm架构的
/usr/lib/gcc-cross/arm-linux-gnueabihf/5/../../../../arm-linux-gnueabihf/bin/ld: warning: libdeps.so, needed by /*.so, not found (try using -rpath or -rpath-link)
需要将相应架构的缺少的.so文件复制到/usr/arm-linux-gnueabi/lib/hf/文件夹
额外补充:Go开发环境搭建Go语言环境
下载
解压
环境变量设置
常用命令
命令行Go私库设置
go env -w GOPROXY=https://goproxy.cn,direct
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)