[关闭]
@Awille 2019-01-12T12:25:38.000000Z 字数 7130 阅读 105

交叉编译 Go Language

交叉编译 go


1、初步认知

交叉编译指的是在一个平台上生成另一个平台的可执行代码

1.1、本地编译

在当前编译平台下,编译本平台运行的可执行程序。

1.2、交叉编译

在当前的编译平台之下,编译出于能在与本地平台体系架构不同的另一种目标平台上运行的程序

2、Go环境搭建

2.1、Golang 下载

Golang下载地址
下载后缀为pkg的文件

2.2、环境变量配置

安装后缀为pkg的文件以后,应该是直接帮助我们设置了环境变量的,我们直接就可以使用go命令了。

go env 可以查看go的环境变量。

  1. GOARCH="amd64"
  2. GOBIN=""
  3. GOEXE=""
  4. GOHOSTARCH="amd64"
  5. GOHOSTOS="darwin"
  6. GOOS="darwin"
  7. GOPATH="/Users/will/go"
  8. GORACE=""
  9. GOROOT="/usr/local/go"
  10. ...
  11. ...

这里的环境变量我自己不设置一下总是觉得很不顺畅,决定还是自己设置一下。
常规操作:
1、vim .bash_profile
2、在文件中添加以下脚本命令:

  1. export GOPATH=/Users/will/go
  2. export GOBIN=$GOPATH/bin
  3. export PATH=$PATH:$GOBIN

3、source .bash_profile

环境变量之后还能正常使用go命令,应该是没有冲突,那我们就那么使用,不管了。

2.3 IDE使用

安装完之后,我们会想有一个IDE来简化我们平时的编码过程,我看到网上有很多,什么sublime添加go语法插件之类,突然发现IntelliJ也有go插件,那么决定以后就使用IntelliJ这个IDE了。在mac环境下,选择intelliJ的preference -> plugin, 搜索go,下载go language插件。结束以后,我们就可以直接创建go工程项目了。

2.4 HelloWorld demo

每次接触一个新语言,第一个要打的程序就是hello world,这里我们也搞一个。
在IntelliJ中创建Go 工程项目。

若要将go编译成一个可执行程序,那么package必须是main,我们就先在项目下创建一个main包,并在下面创建一个go文件

  1. package main
  2. import "fmt"
  3. func main() {
  4. fmt.Println("Hello World!")
  5. }

运行结果:

  1. Hello World!

2.5 不用IDE demo

可以在vsCode当中加入go插件,用vsCode来编辑。
mac go 插件安装指导
依照上面的指导安装各种插件结束以后,打开vsCode,弹出提示更新之类的按照提示走就好了。
创建go 文件,添加以下代码:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. fmt.Println("Hello World!")
  7. }

在命令行中执行该程序:

admindeMacBook-Pro:GoProjects will$ go run HelloWorld.go 
Hello World!

3、go 初步了解

3.1 go中的包

如果想将代码编译成一个可执行程序,那么package必须是main
如果表编译成库,那么package没有限制。
go中所有的代码都应该隶属于一个包

3.2 变量

定义:var 变量名 变量类型
赋值:变量名 = 值
定义变量并初始化:变量名:=值
定义的变量必须得被用到,否则会报错

3.3 函数

函数定义:
func 函数名(参数1 参数1类型,参数2 参数2类型) 返回值类型 {
}

包 变量 函数 例:

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. fmt.Println("Hello World!")
  7. var number int
  8. number = 1
  9. fmt.Println(number)
  10. fmt.Println(add(1, 2))
  11. }
  12. func add(a int, b int) int {
  13. return a + b
  14. }

3.4 golang语言特性:

垃圾回收:

内存自动回收,new分配内存以后不需要手动释放。

天然高并发:

goroute,轻量级线程,可以创建成千上万个goroute
基于CSP模型实现:CSP模型介绍

并发demo,只需在函数前面加一个 go 修饰即可实现并发:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. func main() {
  7. fmt.Println("Hello World!")
  8. for i:=0; i < 5; i ++ {
  9. go add(i, i + 1)
  10. }
  11. }
  12. func add(a int, b int){
  13. time.Sleep(time.Second)
  14. fmt.Println(a + b)
  15. }

运行之后发现打印结果只有HelloWord, 这里是因为主程序main执行结束以后不会等待线程运行结束,直接退出了
我们在main中加一个延迟:

  1. time.Sleep(6 * time.Second)

运行结果:

Hello World!
7
3
5
1
9

可以看到这个并发的过程。

channel 管道

定义管道:

  1. //定义管道 第一个参数chan表明这是一个管道
  2. // 第二个参数表明管道传输的数据类型
  3. // 第三个参数表明管道容量
  4. pipe := make(chan int, 3)

从管道中放入值:

  1. pipe <- value

从管道中取值:

  1. variable <- pipe

管道测试:

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. var pipe = make(chan int, 3)
  7. func main() {
  8. go thread1()
  9. time.Sleep(time.Second)
  10. go therad2()
  11. time.Sleep(time.Second)
  12. }
  13. func thread1() {
  14. pipe <- 1
  15. }
  16. func therad2() {
  17. a:= <- pipe
  18. fmt.Println(a)
  19. }

4、go交叉编译

4.1、go交叉编译优势

go在1.5版本以后改进了对交叉编译的支持,1.5版本以后,go进行跨平台交叉编译变得相当简单,只需要设置两个环境变量GOODS和GOARCH
环境变量:
GOODS:你的应用程序将要运行平台的操作系统。
GOARCH:你的应用程序将要运行平台的处理器架构。
有效组合:

  1. $GOOS $GOARCH
  2. android arm
  3. darwin 386
  4. darwin amd64
  5. darwin arm
  6. darwin arm64
  7. dragonfly amd64
  8. freebsd 386
  9. freebsd amd64
  10. freebsd arm
  11. linux 386
  12. linux amd64
  13. linux arm
  14. linux arm64
  15. linux ppc64
  16. linux ppc64le
  17. linux mips
  18. linux mipsle
  19. linux mips64
  20. linux mips64le
  21. netbsd 386
  22. netbsd amd64
  23. netbsd arm
  24. openbsd 386
  25. openbsd amd64
  26. openbsd arm
  27. plan9 386
  28. plan9 amd64
  29. solaris amd64
  30. windows 386
  31. windows amd64

4.2 编译步骤:

首先设置GOODS和GOARCH两个环境变量,接着执行go build
示例:

  1. env GOOS=linux GOARCH=arm go build 文件

生成指定可执行文件的名字:

  1. env GOOS=linux GOARCH=arm go build -o test.bin

go命令行解释

5 go语言编译java可调用的so库

go有一个go mobile工具直接生成.aar文件供java代码调用。

5.1 安装go mobile

  1. go get golang.org/x/mobile/cmd/gomobile
  2. gomobile init

5.2 在gomobie初始化的时候设置已经安装的ndk路径

因为编译.aar文件是需要用到android 的 ndk的。
如果没有配置ndk,会有下面的报错:

gomobile: no Android NDK path is set. Please run gomobile init with the ndk-bundle installed through the Android SDK manager or with the -ndk flag set.

照着提示去做就好了

gomobile init -ndk "/Users/will/Library/Android/sdk/android-ndk-r15c"

5.3 编写go程序

  1. package goAndroidTest
  2. func ShowResponseFromGo(request string) string {
  3. return "I have reciecved requst: " + request + ", I will process it"
  4. }

这里如果你的函数名是小写会生成出错,想被外部引用必须大写

5.4 编译生成.aar包

在go文件目录下:

  1. gomobile bind -target=android

这里需要注意啊,你一定要把这个编译的文件放在你设置的Gopath的src路径之下,否则你编译会出错的,比如下面的报错:

admindeMacBook-Pro:goAndroidTest will$ gomobile bind -target=android
gomobile: go build -buildmode=c-shared -o=/var/folders/zl/c9s1pvdd78j6n692z0gskpd00000gp/T/gomobile-work-045200192/android/src/main/jniLibs/armeabi-v7a/libgojni.so gobind failed: exit status 1
/var/folders/zl/c9s1pvdd78j6n692z0gskpd00000gp/T/gomobile-work-045200192/src/gobind/go_goAndroidTestmain.go:17:2: local import "." in non-local package

在这个报错当中,我们可以看到这个信息:

go build -buildmode=c-shared -o=/var/folders/zl/c9s1pvdd78j6n692z0gskpd00000gp/T/gomobile-work-045200192/android/src/main/jniLibs/armeabi-v7a/libgojni.so

可以看到这里其实也是生成armeabi-v7a架构下的so文件,我们不用gomobie的bind命令,直接生成so文件也可以的:

正常运行之后,我们可以看到生成的aar文件。

5.5 android中引用生成的aar文件

在我们需要使用的模块当中,将aar放入lib文件夹以后,添加依赖
android字段中添加:

  1. repositories {
  2. flatDir {
  3. dirs 'libs'
  4. }
  5. }

dependence中添加:

  1. implementation(name: 'goAndroidTest', ext: 'aar')

导入后,make一下工程,可以看到在external Library上生成了java文件:

  1. //
  2. // Source code recreated from a .class file by IntelliJ IDEA
  3. // (powered by Fernflower decompiler)
  4. //
  5. package goAndroidTest;
  6. import go.Seq;
  7. public abstract class GoAndroidTest {
  8. private GoAndroidTest() {
  9. }
  10. public static void touch() {
  11. }
  12. private static native void _init();
  13. public static native String showResponseFromGo(String var0);
  14. static {
  15. Seq.touch();
  16. _init();
  17. }
  18. }

5.6 调用

public class MainActivity extends AppCompatActivity {
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e("Awille", GoAndroidTest.showResponseFromGo("i come frome android"));
    }
}

输出:

com.example.gotest E/Awille: I have reciecved requst: i come frome android, I will process it

整体看下来这比不管是用ndk还是用Cmake去生成so库流程上都简单很多。我们不用编写mk或者cmakeList文件,直接生成jar包或者aar,直接调用就可以了。

5.7 生成so库

还是在之前那个包目录下:

  1. go build -buildmode=c-shared -o=libgojni.so

查看生成的文件:

  1. admindeMacBook-Pro:goAndroidTest will$ ls
  2. androidTest.go goAndroidTest.aar
  3. goAndroidTest-sources.jar libgojni.so

可以看到除了之前生成的jar跟aar文件,还生成了刚刚我们指定生成的so库。
目前看下我们的环境变量:

  1. GOARCH="amd64"
  2. GOOS="darwin"

这个darwin操作系统,处理区为amd64架构似乎不能再android中使用,我们重新生成以下so库,配置目标操作系统。

  1. env GOOS=android GOARCH=arm go build -buildmode=c-shared -o=libgojni.so

这里我们生成android系统 arm架构下可以使用的so库。

下面尝试使用这个so库,添加至相应文件中:

  1. static {
  2. System.loadLibrary("gojni");//参数为so库名称
  3. }

定义该方法:

  1. public static native String ShowResponseFromGo(String request);

调用:

Log.e("Awille", MainActivity.ShowResponseFromGo("Hi, how are you"));

查看报错:

java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.example.gotest.MainActivity.ShowResponseFromGo(java.lang.String) (tried Java_com_example_gotest_MainActivity_ShowResponseFromGo and Java_com_example_gotest_MainActivity_ShowResponseFromGo__Ljava_lang_String_2)

说是没有实现,看来在go语言中我们之前写的函数不符合jni的调用规范。

5.8 编译so库

上一节生成的so库无法使用,查了一些博客,做了一些常识:
修改go代码:

  1. package goAndroidTest
  2. import "C"
  3. //export ShowResponseFromGo
  4. func ShowResponseFromGo(request string) string {
  5. return "I have reciecved requst: " + request + ", I will process it"
  6. }

  1. improt "C"
  2. //export ShowResponseFromGo

这里注释的//export ShowResponseFromGo 是必要的,用来做函数的声明

之前用下面的命令,指定GOOS和GOARCH来生成so库,总是会报错 can't load package,这是加载不到import的C库导致的,找这个错误提示的意思,应该是要把C库的整个源代码放在我这个go文件的目录之下,感觉这应该是错误的打包方式,我尝试了各种更换目录,总是不行:

  1. env GOOS=android GOARCH=arm go build -buildmode=c-shared -o=libgojni.so

后来去掉了指定的GOOS跟GOARCH的参数,发现就可以了,但不知道这个生成的so库在java中能不能调用,先尝试下:
新的编译命令:

  1. go build -buildmode=c-shared -o=libgojni.so

尝试了下,依然是crash了,提示的错误同样是:

5.9 通过JNA调用so库

先去下载罪行的JNA依赖():
JNA依赖下载链接

jna链接so库:

21:30:23.291 9264-9264/com.example.gotest E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.gotest, PID: 9264
    java.lang.UnsatisfiedLinkError: Unable to load library 'gojni':
    dlopen failed: can't find ARM symbol
    dlopen failed: can't find ARM symbol
    dlopen failed: "/data/app/com.example.gotest-1/lib/arm/libgojni.so" has bad ELF magic

我特么的就是怀疑生成的so库不对应arm架构,妈的傻逼,妈的傻逼

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注