Djinni 是一个用来生成跨语言的类型声明和接口绑定的工具,主要用于 C++ 和 Java 以及 Objective-C 间的互通。
此文,将介绍如何使用 Djinni 开发 Android, iOS 的共享库。这会带来几个好处:
- 用了接口描述文件。声明清晰、修改简易,并保证了跨平台接口的一致性。
- 自动生成接口绑定代码。免去了绑定 C++ 和 Java (JNI) 及 Objective-C (Objective-C++) 的麻烦。
初见:Djinni 及其样例
下载 Djinni
|
|
编译 Djinni
|
|
于[djinni_root]/src/support/sbt.resolvers.properties
内可添加镜像源。
使用 Djinni
生成样例接口代码:
|
|
即会生成到djinni-output-temp
临时目录,最终复制到generated-src
生成目录。
这里可以看到:依据描述文件example.djinni
,C++ 和 Java 及 Objective-C 的绑定代码都会自动生成好。继续要做的,只是写它们的具体实现,见样例的handwritten-src
目录。
如果要清除输出目录:
|
|
编译样例
[djinni_root]/Makefile
已配置好了依赖,执行相应目标即可。
|
|
注:下载好 Djinni ,即可开始编译样例了。
编译 Android 工程:
|
|
样例的 Android 工程在[example_root]/android
目录, 动态库生成在[example_root]/android/app/libs
目录。或者,利用 Android Studio / Gradle 来运行编译。
编译 iOS 工程:
|
|
样例的 iOS 工程在[example_root]/objc
目录, lib 工程生成在[djinni_root]\build_ios
目录。然后,可以打开[example_root]/objc/TextSort.xcworkspace
来运行编译。
如果要清理工程:
|
|
准备 GYP
编译样例时, Android NDK 与 iOS 的 Library 工程都需依赖 GYP 生成。 make 时,会自行 clone 到[djinni_root]/deps/gyp
目录。
|
|
GYP 生成 Android Makefile 时,目前会遇到如下错误:
ImportError: No module named android
所以,需要切换到旧版本。此后的那个 commit 移除了 Android 的生成器。
|
|
从无到有:Hello Djinni
C++ 接口
定义接口描述文件
|
|
生成接口绑定代码
写了个简单的 Shell 脚本来执行 Djinni 命令,如下:
|
|
其读取了local.properties
内配置的 Djinni 目录路径。
|
|
运行后,代码生成在了generated-src
目录。
|
|
实现 C++ 接口
首先,创建src
目录,存放手写的代码。然后,于子目录cpp
内实现 C++ 接口。
|
|
|
|
C++ 工程
这里用 XCode 创建一个 C++ 工程,来测试 C++ 接口代码。
首先,打开 XCode ,选择”Create a new Xcode project”。然后,选择”Command Line Tool”,来新建命令行工具。
“Next”到下一步时,”Language”选择”C++”。
工程最后保存到了project/cpp
目录。整个文件结构如下:
接下来,把以下 C++ 接口代码文件,拖动到 XCode 工程目录来引入。
|
|
注:取消”Copy items if needed”,选择”Create folder references”。只需引用文件,避免复制。
然后,编写好main.cpp
的代码:
|
|
最后,运行项目,结果如下:
或者,写个project/Cpp.mk
:
|
|
然后,make -f Cpp.mk
编译运行,结果如下:
|
|
iOS 工程
打开 XCode,”File > New > Workspace”新建一个工作区,保存到project/ios
目录。
接着,”File > New > Project”,选择”Single View Application”,创建 iOS 工程。
“Next”到下一步时,”Language”选择”Objective-C”。
工程保存到project/ios
目录,”Add to”选择刚才的工作区。
“Create”完成创建。
生成接口 Libraries 工程
利用 Djinni, GYP 及 Make 生成接口 Libraries 工程。
首先,创建 GYP 文件:
|
|
注意:
1)sources
内的路径必须是相对路径。虽然会识别以/
开头的字符串为绝对路径,但在 XCode 工程内其路径引用是不正确的。
2) GYP 生成 Android Makefile 时,运行命令时的工作目录,必须能够直接子目录到所有代码,包括依赖的 Djinni 的 support-lib 。不然,会报如下错误:AssertionError: Path %s attempts to escape from gyp path %s !)
👌的话,
GypAndroid.mk
会生成到当前工作目录。GYP 生成 Android 时,不允许指定--generator-output
:AssertionError: The Android backend does not support options.generator_output.
所以,简单的解决办法是,文件结构与[djinni_root]/example
一致,并git submodule
Djinni 与 GYP 到工程目录内。
|
|
如果仍想要 Djinni 与 GYP 独立于工程目录外,同时又能够工作在工程目录,那么需要把依赖的东西复制进工程目录。之后,即是这样做的。
接下来,创建 Makefile 文件:
|
|
其也包括了 Android 工程的配置。
注:额外依赖了两个辅助脚本,说明如下:
read_properties.sh
,读取local.properties
内配置的路径。prepare_deps.sh
,准备依赖的文件到指定目录。
然后,运行生成 XCode 的 libhellodjinni 工程。
|
|
其生成在project/build/ios
目录。 XCode 直接打开libhellodjinni.xcodeproj
,即可选择目标进行编译。
但可能此时 libhellodjinni_jni 与 djinni_jni (support_lib) 目标,不能够找到jni.h
。由于 ios 上不需要 jni 绑定,后续也不需依赖,没多大影响。
也让其可通过编译的话,只需要选择目标,在”Build Settings > Header Search Paths”内,添加 Java VM include 就好。
运行如下命令,获得当前的 Java 头文件路径:
|
|
结果,如:”/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers”。
或者,这里找:”/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/include/“。
添加接口 Libraries 依赖
现在,给 iOS 工程上添加上接口 Libraries 的依赖。
首先,打开先前的工作区project/ios/HelloDjinni.xcworkspace
。于左侧项目导航的灰色区域,”Ctrl+Click”或右击打开菜单。选择”Add Files to “HelloDjinni””,添加生成好的libhellodjinni.xcodeproj
和support_lib.xcodeproj
两个库工程。
之后,项目导航选中”HelloDjinni”工程,并选择”HelloDjinni”目标。在”Build Phases”标签页的”Link Binaries With Libraries”选项下,新增 libhellodjinni_objc.a 与 libdjinni_objc.a 的依赖,如下:
在”Build Settings”标签页,找到”Header Search Paths”,添加头文件搜索路径:
|
|
为了兼容 Objective-C++ 桥接代码,需要将HelloWorld/Supporting Files/main.m
重命名为main.mm
。
最终,工作区会类似于下面这样:
完成 UI 并运行
于ViewController.m
内编写代码,创建 UI 并调用接口代码。
|
|
“Product > Run”运行项目。 UI 上”Get Hello Djinni!”的按钮,每点击下就会添加条从 C++ 返回的信息。
Android 工程
Android 工程介绍了两种方式,来整合 NDK Library :
- 一是,使用 GYP 生成的 Android Makefile , Gradle 配置 ndk-build 进行编译。
- 二是,使用 Experimental Plugin ,直接配置成支持 NDK 的工程。
使用 GypMakefile
GYP 生成 Android Makefile ,使用之前写好的project/Makefile
:
|
|
GypAndroid.mk
会生成到父级hellodjinni
目录。
如果还没准备好 Studio 工程,不会继续生成 APK ,会报“找不到android/HelloDjinni/
”。
现在,打开 Android Studio,选择”Start a new Android Studio Project”。
“New”页,”Project Location”存到project/android/HelloDjinni
,如下:
之后,”Target”页选”Phone and Tablet”,”Add”页选”Empty Activity”,最终完成新建。
于”File > Project Structure > SDK Location > Android NDK Location”,设置 NDK 路径:
接下来,独立建一个 app-core Library 模块来引用 C++ 代码。”File > New Module”,选”Android Library”:
然后,修改此 app-core 的build.gradle
,添加引用及 NDK 编译。变更如下:
|
|
项目导航栏切到 Project 视图,在 app-core 下新建jni
目录,创建 NDK 的工程文件。
|
|
|
|
这样,独立的 app-core Library 就👌了。
回到 app ,修改其build.gradle
,以依赖 app-core 。变更如下:
|
|
新建MyApplication.java
,作为自定义 Application。并设置到AndroidManifest.xml
内”application”的”name”字段。
|
|
最终,项目导航 Android 和 Project 视图类似下面这样:
接着,修改 app UI,MainActivity.java
及其布局activity_main.xml
:
|
|
|
|
“Run > Run ‘app’”运行项目。 UI 上”Get Hello Djinni!”的按钮,每点击下就会添加条从 C++ 返回的信息。
使用新试验性插件
Android Studio 1.3 版本开始支持 NDK,需要使用 Experimental Plugin 。这里为当前最新的 0.7.0 版本。
同样,打开 Android Studio,选择”Start a new Android Studio Project”,新建一个”HelloDjinni2”工程。
之后,”Target”页选”Phone and Tablet”,”Add”页选”Empty Activity”,最终完成新建。
于”File > Project Structure > SDK Location > Android NDK Location”,设置 NDK 路径。
接下来,修改成 Experimental Plugin 。先是工程配置:
|
|
接着是 app 模块配置:
|
|
这样, Experimental Plugin 就修改完成了。
接下来,仍旧独立建一个 app-core Library 模块来引用 C++ 代码。”File > New Module”,选”Android Library”:
然后,修改此 app-core 的build.gradle
,支持 Experimental Plugin 并配置 NDK 。如下:
|
|
这样之后,可以在 app-core 下看得jni
目录,包括了所有 C++ 代码。
回到 app ,修改其build.gradle
,以依赖 app-core 。变更如下:
|
|
新建MyApplication.java
,作为自定义 Application。并设置到AndroidManifest.xml
内”application”的”name”字段。
|
|
最终,项目导航 Android 和 Project 视图类似下面这样:
接着,修改 app UI,MainActivity.java
及其布局activity_main.xml
:
|
|
|
|
“Run > Run ‘app’”运行项目。 UI 上”Get Hello Djinni!”的按钮,每点击下就会添加条从 C++ 返回的信息。
NDK 参考
- NDK Guides
- Android NDK Preview
- Experimental Plugin User Guide
- Error: NDK integration is deprecated in the current plugin
结语:开始使用 Djinni 吧
附:源码
Hello Djinni 的源码,这样得到:
|
|
修改local.properties
设好环境。执行make
编译,make clean
清理。
文件结构:
|
|
附:运行环境
|
|
如果未添加过 Android 环境变量,请于 ~/.bash_profile 文件内设置:
|
|
终端运行如下命令可立即生效:
|
|
验证 SDK 与 NDK 命令行工具可用:
|
|
验证 XCode 命令行工具可用:
|
|