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环境变量。

本文参考: Go语言高级编程

基础概念

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/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, 只有 GOARCHarm64 才有效, 表示 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下)

toolarmhfarm64eabi
gccgcc-arm-linux-gnueabihfgcc-aarch64-linux-gnugcc-arm-linux-gnueabi
g++g++-arm-linux-gnueabihfg++-aarch64-linux-gnug++-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

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值