Android NDK与SWIG

本文主要是《Android C++ 高级编程——使用 NDK》的笔记。
国内专门讲 NDK 的书籍寥寥无几,这本貌似是唯一一本翻译的,当然,国外还是有好几本关注 NDK 的书籍,但是都没有中文译本而已。

因为偷懒,实践的时候从网上拷贝的代码,结果某些作者太不靠谱,抄写的代码都是错的,坑死我了。

SWIG 基础

可以参考:

  1. 开发人员 SWIG 快速入门
  2. swig 官网

在 Android 中的使用

ubuntu 14.04 + eclipse

在 jni 文件夹中定义接口文件,SWIG 会基于此接口文件来生成相应的集成代码:

下面是接口文件 Unix.i:

1
2
3
4
5
6
7
8
9
10
11
12
%module Unix

/* unistd.h 是 C 和 C++ 中提供对 POSIX API 支持的头文件 */

%{
#include <unistd.h>
%}

typedef unsigned int uid_t;

extern uid_t getuid(void);

这个时候可以直接调用 swig 来生成集成代码,

1
swig -java -package dev.xesam.ndk -outdir dev/xesam/ndk Unix.i

注意:outdir 一定要事先就存在

不过为了方便,还是直接与 eclipse 整合比较好。在 jni 文件夹定义一个 swig.mk,将 swig 处理单独出来,swig.mk 内容如下:

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
27
28
29
30
31
32
33
34
35
# 定义包名,对应 -package 参数
ifndef MY_SWIG_PACKAGE
$(error MY_SWIG_PACKAGE is not defined.)
endif

# 定义输出目录,对应 -outdir 参数
# subst 表示替换,即用 "/" 替换 包名中的 "."
MY_SWIG_OUTDIR :=$(NDK_PROJECT_PATH)/src/$(subst .,/,$(MY_SWIG_PACKAGE))

# 定义生成文件类型,这里默认是 c
ifndef MY_SWIG_TYPE
MY_SWIG_TYPE := c
endif

# 如果目标源文件是 c++,那么在执行 swig 命令的时候就需要加上 −c++ 参数
ifeq ($(MY_SWIG_TYPE),cxx)
MY_SWIG_MODE := −c++
else
MY_SWIG_MODE :=
endif

# 将生成的 .c 文件加入编译文件中
LOCAL_SRC_FILES += $(foreach MY_SWIG_INTERFACE,$(MY_SWIG_INTERFACES),$(basename $(MY_SWIG_INTERFACE))_wrap.$(MY_SWIG_TYPE))

# 定义 target,每个待生成的 XXX_wrap.c 源文件都依赖与之对应的 XXX.i 接口文件
# 由于 outdir 一定要存在,所以先创建 outdir 目录结构
# $< 表示第一个依赖文件,也就是对应的 XXX.i 接口文件
%_wrap.$(MY_SWIG_TYPE) : %.i
$(call host-mkdir, $(MY_SWIG_OUTDIR))
swig \
-java \
$(MY_SWIG_MODE) \
-package $(MY_SWIG_PACKAGE) \
-outdir $(MY_SWIG_OUTDIR) \
$<

注意,按照 Makefile 的规范来写,特别是空格与 TAB 的区别。

上面定义的 MY_SWIG_PACKAGE 等变量都定义在 Android.mk 中,将 swig.mk 加入到 Android.mk 即可。Android.mk 内容如下;

1
2
3
4
5
6
7
8
9
10
11
12
13
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS) # 清除除了 LOCAL_PATH 之外的 LOCAL_* 变量

LOCAL_MODULE := hello-ndk # 设定一个唯一的名称

MY_SWIG_PACKAGE := dev.xesam.ndk
MY_SWIG_INTERFACES := Unix.i
MY_SWIG_TYPE := c

include $(LOCAL_PATH)/swig.mk

include $(BUILD_SHARED_LIBRARY)

定义完毕之后,工程大致结构如下:

project_dir
    |--src
    |--jni
        |--Android.mk
        |--Application.mk
        |--swig.mk
        |--Unix.i
    

在项目根目录运行

1
ndk-build

输入大致如下:

1
2
3
4
5
6
7
8
9
10
11
mkdir -p  ./src/dev/xesam/ndk
swig \
-java \
\
-package dev.xesam.ndk \
-outdir ./src/dev/xesam/ndk \
jni/Unix.i
[armeabi] Compile thumb : hello-ndk <= Unix_wrap.c
[armeabi] SharedLibrary : libhello-ndk.so
[armeabi] Install : libhello-ndk.so => libs/armeabi/libhello-ndk.so

运行完毕之后,工程大致结构如下:

project_dir
    |--src
        |--dev
            |--xesam
                |--ndk
                    |--Unix.java
                    |--UnixJNI.java
    |--jni
        |--Android.mk
        |--Application.mk
        |--swig.mk
        |--Unix.i
        |--Unix_wrap.c
    |--libs
        |--armeabi
            |--libhello-ndk.so
            

libhello-ndk.so 可以在工程代码直接使用,当然,生成的 UnixJNI.java 需要补充 loadLibrary 调用:

1
2
3
4
5
6
public class UnixJNI {
...
static{
System.loadLibrary("hello-ndk");
}
}

测试Activity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

import dev.xesam.ndk.Unix;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(Unix.getuid() + "");
}
}