当前位置:   article > 正文

go语言调用c语言动态库及交叉编译_cgo 交叉编译

cgo 交叉编译

实现基础: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++对应的源文件。

举个最简单的例子:

  1. package main
  2. /*
  3. #include <stdio.h>
  4. void printint(int v) {
  5. printf("printint: %d\n", v);
  6. }
  7. */
  8. import "C"
  9. import "unsafe"
  10. func main() {
  11. v := 42
  12. C.printint(C.int(v))
  13. }

这个例子展示了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语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。

  1. // #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
  2. // #cgo LDFLAGS: -L/usr/local/lib -lpng
  3. // #include <png.h>
  4. 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内容如下:

  1. #ifndef CoreServer__h
  2. #define CoreServer__h
  3. #define MASTER_INNER 0
  4. #define MASTER_OUTTER 1
  5. #define SLAVER_INNER 2
  6. #define SLAVER_OUTER 3
  7. #ifdef __cplusplus
  8. extern "C"
  9. {
  10. #endif
  11. /**
  12. * @brief GetServerIP 获取服务器IP
  13. * @param sType 服务器类型取值范围0~3 例:MASTER_INNER
  14. * @param sIP 返回获取的服务器ip
  15. * @return 1 成功 -1 失败
  16. */
  17. int GetServerIP(int sType,char* sIP);
  18. #ifdef __cplusplus
  19. }
  20. #endif
  21. #endif
Go项目文件结构

在项目根目录下创建include文件夹,放置头文件CoreServer.h,创建lib文件夹,放置libcoreserver.so
在这里插入图片描述

应用源码
参数设置

通过#cgo语句设置编译阶段和链接阶段的相关参数

  1. /*
  2. #cgo CFLAGS : -I../include
  3. #cgo LDFLAGS: -L../lib -lcoreserver -lm
  4. #include "CoreServer.h"
  5. */
  6. import "C"

CFLAGS部分,-I定义了头文件包含的检索目录。LDFLAGS部分,-L指定了链接时库文件检索目录,-l指定了链接时需要链接coreserver库。
因为C/C++遗留的问题,C头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径。当前项目未确定库文件的路径,先暂时使用相对目录。运行时,会提示找不到库文件。

  1. GOROOT=/home/kylin/go #gosetup
  2. GOPATH=/home/kylin/go-space #gosetup
  3. /home/kylin/go/bin/go build -o /tmp/___1go_build_main_go /home/kylin/go-space/src/insight-client/main.go #gosetup
  4. /tmp/___1go_build_main_go
  5. /tmp/___1go_build_main_go: error while loading shared libraries: libcoreserver.so: cannot open shared object file: No such file or directory
  6. 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
  1. # libc default configuration
  2. /usr/local/lib
  3. /home/kylin/go-space/src/insight-client/lib

重点!!!!最后将修改写入缓存!!

sudo ldconfig
方法调用

在Go中调用C的GetServerIP方法,获取主服务器IP地址。重点难点在于Go语言和C语言的数据类型转换。

  1. func getInsightServerConfigure() (bool, string, error) {
  2. bt := make([]byte, 64)
  3. masterIp := (*C.char)(unsafe.Pointer(&bt[0]))
  4. //获取主服务器IP
  5. //#define MASTER_INNER 0
  6. //#define MASTER_OUTTER 1
  7. //#define SLAVER_INNER 2
  8. //#define SLAVER_OUTER 3
  9. serverConfigureType, err := config.Int("insight-server-configure-type")
  10. if err != nil {
  11. logs.Warning("获取使用平台配置的服务器地址的类型配置(insight-server-configure-type)失败,默认使用MASTER_INNER主服务器内网IP,为0", err)
  12. serverConfigureType = 0
  13. }
  14. configureType := C.int(serverConfigureType)
  15. r := C.GetServerIP(configureType, masterIp)
  16. if r == 1 {
  17. ip := GetStringByByte(bt)
  18. logs.Info("获取的主服务器ip为:", ip)
  19. if len(strings.Split(ip, ".")) == 4 {
  20. InsightServerIp = ip
  21. //设置insight核心服务IP地址配置
  22. config.Set("insight-server-ip", InsightServerIp)
  23. //全局设置insight核心服务地址
  24. InsightServerAddress = "http://" + InsightServerIp + ":" + InsightServerPort + InsightServerContextPath
  25. logs.Info("insight-server服务地址为:", InsightServerAddress)
  26. return true, "", nil
  27. } else {
  28. err := errors.New("获取配置的服务器IP地址不正确")
  29. return false, "获取配置的服务器IP地址不正确", err
  30. }
  31. } else {
  32. err := errors.New("获取配置的服务器IP地址失败")
  33. return false, "获取配置的服务器IP地址失败", err
  34. }
  35. }

交叉编译

说明: 以下的交叉编译主机是在 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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/173731
推荐阅读
相关标签
  

闽ICP备14008679号