{ compile }

  • 任务1.1:编译SensorGraph

    |

    金老师给我们本周的任务编译Android NDK示例中的SensorGraph。如果已经做过任务1的话,这个任务应该会很简单。

    从Github上clone源代码,将SensorGraph项目导入Android Studio,Gradle Sync & Build即可。

    运行屏幕截图
  • 任务1:Android与交叉编译

    |

    金老师这周给我们布置的任务是:“交叉编译一个共享库(Shared Object),在安卓手机中加载它,显示一个Hello, world。”

    要实现这一个目标有很多种途径,最简单的应该算是使用Android Studio中集成好的NDK。Android开发基本上离不开Android Studio。

    NDK是额外组件,需要通过Android Studio中的SDK Manager进行安装。安装时还可以勾选构建工具CMake和调试用的LLDB。

    安装完所有需要的组件后就万事俱备了。凭空将C++引入Android开发,最简单的方法就是从Native C++模板新建Android项目。

    不过,从Native C++模板新建的项目有可能会默认选择其他NDK版本而拒绝使用当前的安装的NDK。这时,在模块的build.gradle配置文件中将NDK的版本指定为当前安装的版本即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    apply plugin: 'com.android.application'

    android {
    compileSdkVersion 28
    defaultConfig {
    // ...
    }

    buildTypes {
    // ...
    }

    // 在这里指定NDK版本。
    ndkVersion "21.0.6113669"

    externalNativeBuild {
    cmake {
    path "src/main/cpp/CMakeLists.txt"
    version "3.10.2"
    }
    }
    }

    dependencies {
    // ...
    }

    模板已经将Gradle所需的全部依赖完整配置好了,并自动在MainActivity中引用了一个native String stringFromJNI()方法。直接修改这个方法的实现就算是完成任务了。

    这里也可以再多做一层封装:

    在Project视图下的app/cpp里新建先定义C++头文件hello.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #ifndef HI_NDK_HELLO_H
    #define HI_NDK_HELLO_H

    #include <string>
    using std::string;

    /**
    * 用英语和中文说“世界你好。”
    * @return "Hello, world! 世界你好!"
    */
    string sayHi();

    #endif //HI_NDK_HELLO_H

    编写对应的C++实现文件hello.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include "hello.h"
    #include <android/log.h>
    using namespace std;

    /**
    * 用英语和中文说世界你好,以期检验JNI中的编码问题。
    * @return "Hello, world! 世界你好!"
    */
    string sayHi() {
    string greeting = string("Hello, world! 世界你好!");

    // 在Logcat中可以看到输出。
    __android_log_write(android_LogPriority::ANDROID_LOG_INFO, "Hi", greeting.c_str());
    return greeting;
    }

    并将上述.cpp文件加入CMakeLists.txtadd_library处理器中,跟模板生成的native-lib.cpp放在一起。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # CMakeLists.txt
    # ...

    add_library( # Sets the name of the library.
    native-lib

    # Sets the library as a shared library.
    SHARED

    # Provides a relative path to your source file(s).
    native-lib.cpp hello.cpp )

    最后在native-lib.cpp中调用新建的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include "hello.h"
    #include <jni.h>
    #include <string>

    extern "C"
    JNIEXPORT jstring JNICALL
    Java_net_qfstudio_hindk_MainActivity_getGreeting(JNIEnv *env, jobject thiz) {
    return env->NewStringUTF(sayHi().c_str());
    }

    执行Gradle Build并连接手机或模拟器运行,即可在Logcat和MainActivity屏幕中看到预期的输出。

    运行屏幕截图

    完整代码可见于Gitee

  • 任务0.1:编译GCC

    |

    金老师给我们的附加任务是从源代码编译一套GCC。

    GNU网站上有完整的关于如何从源代码编译GCC的指导。GNU网站上将编译和安装GCC的过程划分为6个步骤,我们只需要完成前4个步骤(即到Build这一步)就算是完成任务了。

    我打算在云主机中编译GCC。总的来说,这不算是一件难事。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 在编译前确认系统信息。
    $ uname -r
    3.10.0-1062.12.1.el7.x86_64

    $ gcc --version
    gcc (GCC) 8.3.1 20190311 (Red Hat 8.3.1-3)

    $ cat /proc/cpuinfo
    model name : Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz
    cpu cores : 1

    取得GCC的源代码

    这一部分对应指南中的第2步

    我跳过了指南中的第1步,因为第1步描述的是编译GCC所需要安装的软件。这些软件如果缺失的话,可以在“配置”这一步骤中检测出来。

    GCC的源代码可以从GNU的官方Git仓库中克隆下来,或者从镜像网站下载。这里我选择的是速度较快的日本镜像站ftp.tsukuba.wide.ad.jp,下载的版本是9.2.0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    cd ~
    mkdir -p esit-workspace/task0.1
    cd esit-workspace/task0.1

    wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-9.2.0/gcc-9.2.0.tar.gz
    # 下载时如果速度太慢或失败,可以现在网络条件好的地方下载好,然后使用`scp`上传至服务器。

    # 从压缩包中解压源代码。
    tar -xf gcc-9.2.0.tar.gz

    这样,GCC的源代码就准备好了。

    配置

    这一部分对应指南中的第3步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    $ pwd
    /home/lightyears/esit-workspace/task0.1
    $ ls
    gcc-9.2.0 gcc-9.2.0.tar.gz

    # 根据指南的要求,新建一个目录用来装编译产物。
    $ mkdir gcc-9.2.0-build
    gcc-9.2.0 gcc-9.2.0-build gcc-9.2.0.tar.gz

    # 运行配置脚本。
    $ cd gcc-9.2.0-build
    $ ../gcc-9.2.0/configure
    configure: error: Building GCC requires GMP 4.2+, MPFR 2.4.0+ and MPC 0.8.0+.
    Try the --with-gmp, --with-mpfr and/or --with-mpc options to specify
    their locations. Source code for these libraries can be found at
    their respective hosting sites as well as at
    ftp://gcc.gnu.org/pub/gcc/infrastructure/. See also
    http://gcc.gnu.org/install/prerequisites.html for additional info. If
    you obtained GMP, MPFR and/or MPC from a vendor distribution package,
    make sure that you have installed both the libraries and the header
    files. They may be located in separate packages.

    configure在运行的过程中报错,看上去应该是必须的一些软件没有安装。依次执行yum search/info GMPyum search/info MPFRyum search MPCyum info libmpc,发现应该是MPC包没有安装,sudo yum install libmpc将需要的包装好,再次执行配置。

    1
    2
    $ ../gcc-9.2.0/configure
    configure: error: I suspect your system does not have 32-bit development libraries (libc and headers). If you have them, rerun configure with --enable-multilib. If you do not have them, and want to build a 64-bit-only compiler, rerun configure with --disable-multilib.

    由于不需要进行编译32位的程序,因此可以使用--disable-multilib重新配置。

    1
    2
    $ ../gcc-9.2.0/configure
    # 程序运行后退出,MakeFile已经生成好了。

    到这里,这一步骤就顺利结束了。

    编译

    这一部分对应指南中的第4步

    由于编译过程会持续很久,这时可以使用screen这样的终端管理器。这样即使断开SSH连接,编译过程也继续运行。

    1
    2
    3
    screen -S compile-gcc # 新建一个窗口提供给编译gcc使用。
    make -j 2 # 然后等待。
    # 可随时断开SSH连接。

    中途可以重新连接上主机看一下编译过程是否出现差错。

    1
    2
    3
    4
    5
    6
    # 当重新通过SSH连接到主机时,使用screen恢复之前的会话。
    screen -r compile-gcc

    # 顺便一提,在Screen会话中时,按下`Ctrl + A, d`组合键可以从当前会话中分离。
    # (在Screen会话中时,)按下`Ctrl + A, k`可以Kill掉当前会话。
    # (在Screen会话中时,)按下`Ctrl + A, ?`可以显示所有可用的命令。

    在我的主机上,编译过程持续了7个小时。

    编译完成后就可以准备安装了。本来还应该进行测试步骤,但因为测试耗费时间太长,跳过这一步吧。

    为避免覆盖系统的GCC,make install时指定一个目录进行安装。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $ screen -r compile-gcc
    $ cd ~/esit-workspace/task0.1/ && ls
    gcc-9.2.0 gcc-9.2.0-build gcc-9.2.0.tar.gz

    # 创建一个目录,下一步`make install`的GCC就装在这个新建的目录里。
    $ mkdir gcc-9.2.0-target && ls
    gcc-9.2.0 gcc-9.2.0-build gcc-9.2.0-target gcc-9.2.0.tar.gz

    $ cd gcc-9.2.0-build
    $ make DESTDIR=/home/lightyears/esit-workspace/task0.1/gcc-9.2.0-target install

    make install结束后用热乎着的编译器编译一个新的Hello, world看看。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ cd ../gcc-9.2.0-target && pwd
    /home/lightyears/esit-workspace/task0.1/gcc-9.2.0-target

    $ ./usr/local/bin/gcc --version
    gcc (GCC) 9.2.0

    $ nano hello-world.c # 写一个Hello, world。

    $ ./usr/local/bin/gcc -v hello-world.c -o hello-world > compile.log 2>&1

    # 查看一下编译日志。
    $ grep -z "/home/lightyears/esit-workspace/task0.1/gcc-9.2.0-target/" compile.log

    $ ./hello-world
    Hello, world!

    好,大功告成。


    编译产生的中间产物很多,安装完成后就不需要这些文件了。若是磁盘空间紧俏,可以直接删除。

    1
    2
    3
    4
    5
    6
    7
    $ du ~/esit-workspace/task0.1/* -sh
    804M /home/lightyears/esit-workspace/task0.1/gcc-9.2.0
    6.1G /home/lightyears/esit-workspace/task0.1/gcc-9.2.0-build
    1.5G /home/lightyears/esit-workspace/task0.1/gcc-9.2.0-target
    119M /home/lightyears/esit-workspace/task0.1/gcc-9.2.0.tar.gz

    rm -rf ~/esit-workspace/task0.1/gcc-9.2.0-build
© 2021 - lightyears1998
Powered by Hexo , Theme - Icalm