go语⾔调⽤c语⾔动态库及交叉编译
实现基础:CGO编程
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环境变
量。
本⽂参考:
基础概念
import "C"语句
如果在Go代码中出现了import "C"语句则表⽰使⽤了CGO特性,紧跟在这⾏语句前⾯的注释是⼀种特殊语法,⾥⾯包含的是正常的C语⾔代码。当确保CGO启⽤的情况下,还可以在当前⽬录中包含C/C++对应的源⽂件。
举个最简单的例⼦:
package main
/*
#include <stdio.h>
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中类型的变量。
#cgo语句
在import "C"语句前的注释中可以通过#cgo语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要⽤于定义相关宏和指定头⽂件检索路径。链接阶段的参数主要是指定库⽂件检索路径和要链接的库⽂件。
// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include <png.h>
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/ #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/f⽂件,在新的⼀⾏中加⼊库⽂件所在⽬录;
运⾏ldconfig,以更新/etc/ld.so.cache⽂件;
⽅法2:
go语言开发环境搭建在/etc/f.d/⽬录下新建任何以.conf为后缀的⽂件,在该⽂件中加⼊库⽂件所在的⽬录;
运⾏ldconfig,以更新/etc/ld.so.cache⽂件;
第⼆种办法更为⽅便,对于原系统的改动最⼩。因为/etc/f⽂件的内容是include /etc/f.d/*.conf
所以,在/etc/f.d/⽬录下加⼊的任何以.conf为后缀的⽂件都能被识别到。
本⽂修改的/etc/f.d路径下的f⽂件
/etc/f.d$ sudo f
# 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 =""+ 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 平台下进⾏的.
参考⽂章
编译参数讲解
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), 编译⽬标⽂件使⽤的创建库⽂件命令.
交叉汇编 linux 系统 arm64 架构的⽬标⽂件
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 arm64eabi 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
⼿动下载安装(⼿动安装在系统版本不⼀致的情况下会提⽰缺少依赖,很难安装成功)
编译常见错误
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论