前景补充:
- GUN是自由操作系统计划–GNU is Not Unix
- 1990有了GCC-GNU Compiler Collection和大部分UNIX系统的程序库和工具
- 1991年Linus Torvalds编写出了与UNIX兼容的Linux操作系统内核并在GPL条款下发布。
- GUN MAKE也是其中之一
- 操作系统屏蔽硬件差异(cpu架构等),软件只对应不同操作系统(再分个位数)
任务?:linux下写下.out.elf.a.so各种文件格式
关于ABI
ABI(Application Binary Interface): 应用程序二进制接口 描述了应用程序和操作系统之间,一个应用和它的库之间,或者应用的组成部分之间的低接口。
ABI涵盖了各种细节:
- 数据类型的大小、布局和对齐;
- 调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
- 系统调用的编码和一个应用如何向操作系统进行系统调用;
- 以及在一个完整的操作系统ABI中,目标文件的二进制格式、程序库等等。
ABI不同于API ,API定义了源代码和库之间的接口,因此同样的代码可以在支持这个API的任何系统中编译 ,然而ABI允许编译好的目标代码在使用兼容ABI的系统中无需改动就能运行。 ABI掩盖了各种细节,例如:调用约定控制着函数的参数如何传送以及如何接受返回值;系统调用的编码和一个应用如何向操作系统进行系统调用;以及在一个完整的操作系统ABI中,对象文件的二进制格式、程序库等等。一个完整的ABI,像 Intel二进制兼容标准 (iBCS) ,允许支持它的操作系统上的程序不经修改在其他支持此ABI的操作系统上运行。其他的 ABI 标准化细节包括C++ name decoration和同一个平台上的编译器之间的调用约定,但是不包括跨平台的兼容性。在Unix的操作系统中,存在很多运行在同一件平台上互相相关但是不兼容的操作系统(尤其是80386兼容系统)。有一些努力尝试标准化A I,以减少销售商将程序移植到其他系统时所需的工作。然而,直到现在还没有很成功的例子,虽然LSB正在为Linux做这方面的努力。
一套完整的ABI(比如:Intel Binary Compatibility Standard (iBCS)),可以让程序在所有支持该ABI的系统上运行,而无需对程序进行修改。
也就是说,操作系统屏蔽硬件差异,但是程序执行除了api外还有许多其他约定:数据对齐,调用约定等。所以直接复制程序仍不一定能够执行。ABI是为了统一这些的。
api是系统屏蔽底层硬件的。使程序可以编辑一次多平台编译。abi是屏蔽运行环境的,使程序编译一次可以多平台运行
关于java-jni
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。
思考:关于jvm的实现过程。java命令在win下是exe。那么具体到那些函数是交给本地代码呢?已知有loadclass
jni步骤-win
编写native方法的类:
12345678910111213package a;public class b{public native void testHello();public static void main(String[] args){System.loadLibrary("B");b jniDemo = new b();jniDemo.testHello();}}javac生成.class文件(java的一系列命令都在与包同目录下,而java的包已文件形式表示)具体命令为javac a/b.java 包下用/后加.java
javah -classpath . -jni a.b 生成.h头文件用于实现(此处坑较多,网上一般的直接javah a.b在我这里都不好用后来这样全部指明才可以)
javah -classpath . -jni a.b 解决。-classpath是加载类的路径,jni是生成的标准。(加载什么)注意包下用.且不加后缀。生成:a_b.h其只与包、类和方法名有关123456789101112131415161718192021/* DO NOT EDIT THIS FILE - it is machine generated */#include "jni.h"/* Header for class a_b */#ifndef _Included_a_b#define _Included_a_b#ifdef __cplusplusextern "C" {#endif/** Class: a_b* Method: testHello* Signature: ()V*/JNIEXPORT void JNICALL Java_a_b_testHello(JNIEnv *, jobject);#ifdef __cplusplus}#endif#endif%JAVA_HOME%/include下的jni.h放于其下
编写随意名字的dll文件(java类中System.loadLibrary(“”);同名即可):B.cpp:
12345678#include "a_b.h"#include <iostream>#include <stdio.h>JNIEXPORT void JNICALL Java_a_b_testHello(JNIEnv *, jobject) {printf("this is C++ print");}编译该dll,生成时指定平台为64位的(因为下的java是64的)
- 将dll置于.class同目录下。最终结果为俩文件:b.class与B.dll 由此可见.h文件其实可以不用自动生成,只是方便知道函数名字而已。
- java a.b成功
NDK
基本概念
- native-原生:指的是用原生代码编写android程序
- NDK中对应于不同cpu框架的固定平台c/c++代码,执行格式为linux下的.so与.a与.elf,采用的是交叉编译,生成运行于其它平台的代码
- 使用 NativeActivity 的 Android 应用仍会在其自己的虚拟机中运行,与其他应用以沙盒分隔。(android应用运行.so的过程?依靠虚拟机加载调用本地动态库.so?)
- 以前编写原生程序一般用的是ndk提供的NDK-build。当然也可以单纯使用gcc(了解项目管理工具与gcc的使用?android是make)AS使用构建原生库的默认工具是CMake,也可以用ndk-build
- 通过ABI指定生成的平台,默认下在lib中会生成多平台的.so(操作系统屏蔽底层的疑问见ABI说明)
- 与原生的jni不同的是,as中没有生成.h头文件,但仍需jni.h可见仍使用的是jni调用,但是省略了繁杂的步骤,交给编译器as直接确定.so
AS下编辑native—较新方法-使用cmake(常用!)
官方AS的文档十分详细。。
AS可以先写好java类,然后点击错误修正可以自动生成对应c文件与原生函数头。
AS生成的:注意as下如果是cpp文件extern “C” 必加
当你使用错误修正时是生成c版本的时自动不加,当是cpp文件中错误修正时自动加
CMake快速上手:
创建项目
创建支持原生代码的项目与创建任何其他 Android Studio 项目类似,不过前者还需要额外几个步骤:
- 在向导的 Configure your new project 部分,选中 Include C++ Support 复选框。点击 Next。
- 正常填写所有其他字段并完成向导接下来的几个部分。
- 在向导的 Customize C++ Support 部分,您可以使用下列选项自定义项目:
C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。
Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
点击 Finish。
目录
- 在 cpp 组中,您可以找到属于项目的所有原生源文件、标头和预构建库。对于新项目,Android Studio 会创建一个示例 C++ 源文件 native-lib.cpp,并将其置于应用模块的 src/main/cpp/ 目录中。本示例代码提供了一个简单的 C++ 函数 stringFromJNI(),此函数可以返回字符串“Hello from C++”。
- 在 External Build Files 组中,您可以找到 CMake 或 ndk-build 的构建脚本。与 build.gradle 文件指示 Gradle 如何构建应用一样,CMake 和 ndk-build 需要一个构建脚本来了解如何构建您的原生库。对于新项目,Android Studio 会创建一个 CMake 构建脚本 CMakeLists.txt,并将其置于模块的根目录中。且必须叫这个名字。
- cmake与ndk-build的配置都在build.gradle的管理配置下。
运行过程
- Gradle 调用您的外部构建脚本 CMakeLists.txt。
- CMake 按照构建脚本中的命令将 C++ 源文件 native-lib.cpp 编译到共享的对象库中,并命名为 libnative-lib.so,Gradle 随后会将其打包到 APK 中。
- 运行时,应用的 MainActivity 会使用 System.loadLibrary() 加载原生库。现在,应用可以使用库的原生函数 stringFromJNI()。
- MainActivity.onCreate() 调用 stringFromJNI(),这将返回“Hello from C++”并使用这些文字更新 TextView。
CMakeLists.txt
使用ndk库
Android NDK 提供了一套实用的原生 API 和库。通过将 NDK 库包含到项目的 CMakeLists.txt 脚本文件中,您可以使用这些 API 中的任意一种。
预构建的 NDK 库已经存在于 Android 平台上,因此,您无需再构建或将其打包到 APK 中。由于 NDK 库已经是 CMake 搜索路径的一部分,您甚至不需要在您的本地 NDK 安装中指定库的位置 - 只需要向 CMake 提供您希望使用的库的名称,并将其关联到您自己的原生库。
将 find_library() 命令添加到您的 CMake 构建脚本中以定位 NDK 库,并将其路径存储为一个变量。您可以使用此变量在构建脚本的其他部分引用 NDK 库。
动态加载:为了确保您的原生库可以在 log 库中调用函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令关联库。让你的.cpp文件可用ndk库
静态加载:NDK 还以源代码的形式包含一些库,您在构建和关联到您的原生库时需要使用这些代码。您可以使用 CMake 构建脚本中的 add_library() 命令,将源代码编译到原生库中。要提供本地 NDK 库的路径,您可以使用 ANDROID_NDK 路径变量,Android Studio 会自动为您定义此变量。
添加自己的文件
用 add_library() 向您的 CMake 构建脚本添加源文件或库
CMake 使用以下规范来为库文件命名:
lib库名称.so
例如,如果您在构建脚本中指定“native-lib”作为共享库的名称,CMake 将创建一个名称为 libnative-lib.so 的文件。不过,在 Java 代码中加载此库时,请使用您在 CMake 构建脚本中指定的名称:
AS下编辑native—过去的方法使用ndk-build
ndk-build使用的是众多make文件其中大部分在ndk\build\core中,我们需要提供俩个:Android.mk与Application.mk
ndk-build通过这些确定生成so
过程
- 创建一个新的project,一定要勾选“include C++ support”
注: 项目默认是用的是CMake,并不会自动生成Android.mk 在cpp目录下新建Android.mk示例代码如下:
12345LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := xxxx注意这里是生成.so的名字,会自动生成lib前缀LOCAL_SRC_FILES := native-lib.cppinclude $(BUILD_SHARED_LIBRARY)修改build.gradle文件,设置使用ndk-build,而不是cmake(在这里改变编译c++部分的工具)
12345678910111213141516171819defaultConfig {...externalNativeBuild {ndk {ldLibs "log"abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "mips"}}}...externalNativeBuild {ndkBuild {path "src/main/cpp/Android.mk"}}如果要使用C++的库,则创建Application.mk文件(同样置于cpp目录),添加如下代码
APP_STL := stlport_static