0%

Android JNI调用C/C++程序

JNI是Java Native Interface的缩写,中文为JAVA本地调用。使用JNI可以很方便的用我们的Java程序调用C/C++程序。很多时候,某些功能用Java无法实现,比如说涉及到底层驱动的一些功能,这时候我们就可以利用JNI来调用C或者C++程序来实现,这就是JNI的强大之处。但是JNI也有它的缺点,使用java与本地已编译的代码交互,通常会丧失平台可移植性。

在Android中调用C/C++程序也是一样的,基本和Java调用的方式一致,只是Android使用NDK编译出动态链接库,供Androd程序调用。本文以一个简单的Android JNI调用程序来讲解整个过程。

NDK是Google提供了交叉编译工具链, 能够在linux/Windows平台编译出在arm平台下执行的二进制库文件.

编写Java类

创建Java类,在里面定义一个本地方法(用native关键字修饰的方法);

1
2
3
4
5
6
7
8
9
10
package com.example.androidjnitest;  

public class Hello {
static // 静态加载动态库
{
System.loadLibrary("Hello");
}

public native int testJni(int num); // native方法,在其实现中调用C/C++程序
}

使用javah生成头文件

以eclipse为例,加入到项目的src目录,运行javah com.example.androidjnitest.Hello,如果目录不正确,可能会出现找不到类文件的情况。正常情况会生成下面的com_example_androidjnitest_Hello.h头文件,具体文件名与包名相关。文件的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_androidjnitest_Hello */

#ifndef _Included_com_example_androidjnitest_Hello
#define _Included_com_example_androidjnitest_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_androidjnitest_Hello
* Method: testJni
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_example_androidjnitest_Hello_testJni
(JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif

函数声明的形式为Java_完整类名_方法名, 完整类名包括了包名。

注意:

  • 此头文件不需要用户编辑,是直接供其它C、C++程序引用的。
  • 此头文件中的函数方法名,是将来与动态链接库交互的接口,并需要名字保持一致。

在注释中我们可以看到 Signature, 这个是方法的签名,用于指明函数的返回类型。关于不同的Signature, 见下表:

实现头文件中的函数

下面以简单的C程序为例来说明,C++在转换字符串等操作时略有不同。本例中C/C++编写都没有区别。

在实现对应.h文件时,可以在当前项目里面新建一个jni文件夹,将之前的.h文件和将要实现的.c文件放在该文件夹中。

1
2
3
4
5
6
7
8
9
10
11
#include <jni.h>
#include "com_example_androidjnitest_Hello.h"
#include <stdio.h>

JNIEXPORT jint JNICALL Java_com_example_androidjnitest_Hello_testJni
(JNIEnv *env, jobject obj, jint num)
{
printf("From JNI !");

return num*num;
}

上面是一个很简单的例子,如果工程比较大,可能需要在.c文件中包含很多其他C的头文件,并在每个函数实现中做一些类型转换之类的。

JNIEnv结构体系 : JNIEnv指针指向一个线程相关的结构,线程相关结构指向一个指针数组,指针数组中的每个元素最终指向一个JNI函数。jni.h中声明了JNIEnv : C语言中定义的JNIEnv 是 JNINativeInterface* , C++中定义的JNIEnv 是 _JNIEnv。

1
2
3
4
5
6
7
8
9
10
11
struct _JNIEnv;  
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus) //为了兼容C 和 C++两种代码 使用该 宏加以区分
typedef _JNIEnv JNIEnv; //C++ 中的JNIEnv类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;//C语言中的JNIEnv类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif

关于JNIEnv指针调用解析 :

  • C中JNIEnv就是 const struct JNINativeInterface*, JNIEnv * env等价于 JNINativeInterface** env, 因此要得到JNINativeInterface结构体中定义的函数指针, 就必须先获取到 JNINativeInterface的一级指针对象 即 *env , 该一级指针对象就是 JNINativeInterface* env, 然后通过该一级指针对象调用JNI函数 : (*env)->NewStringUTF(env, "hello");
  • C++ 中的JNIEnv就是 _JNIEnv 结构体, 二者是等同的; 因此在调用 JNI函数的时候, 只需要使用env->NewStringUTF(env, "hello")方法即可, 不用在进行*运算;

在JNINativeInterface结构体中定义了一系列的关于Java操作的相关方法 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 
* Table of interface function pointers.
*/
struct JNINativeInterface {
void* reserved0;
void* reserved1;

... ...

jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID,
va_list);
jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID,
jvalue*);
jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...);
jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list);

... ...

void* (*GetDirectBufferAddress)(JNIEnv*, jobject);
jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject);

/* added in JNI 1.6 */
jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject);
};

更多的函数可以参考官方文档:JNI Functions

NDK编译生成so库

jni文件夹下建立Android.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
36
37
# Copyright (C) 2009 The Android Open Source Project

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

# http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := Hello

LOCAL_SRC_FILES := com_example_androidjnitest_Hello.c

include $(BUILD_SHARED_LIBRARY)

makefile中变量解释:
LOCAL_PATH := $(call my-dir)
一个 Android.mk 文件首先必须定义好 LOCAL_PATH 变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含 Android.mk file 文件的目录)。
include $( CLEAR_VARS)
CLEAR_VARS 由编译系统提供,指定让 GNU MAKEFILE 为你清除许多 LOCAL_XXX 变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等…), 除 LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个 GNU MAKE 执行环境中,所有的变量都是全局的。
LOCAL_MODULE := Hello
编译的目标对象,LOCAL_MODULE 变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为hello的共享库模块,将会生成libhello.so文件。
LOCAL_SRC_FILES := com_example_androidjnitest_Hello.c
LOCAL_SRC_FILES 变量必须包含将要编译打包进模块中的 C 或 C++ 源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。
include $(BUILD_SHARED_LIBRARY)
BUILD_SHARED_LIBRARY 表示编译生成共享库,是编译系统提供的变量,指向一个 GNU Makefile 脚本,负责收集自从上次调用include $(CLEAR_VARS)以来,定义在 LOCAL_XXX 变量中的所有信息,并且决定编译什么,如何正确地去做。还有 BUILD_STATIC_LIBRARY 变量表示生成静态库:lib$(LOCAL_MODULE).aBUILD_EXECUTABLE 表示生成可执行文件。

接着在makefile所在目录,使用ndk-build编译生成so库,如果没有问题,可以看到so库存放的位置,如果有语法错误,进行相应的修改。

至此生成so的步骤完成,接下来可以在程序中测试so是否运行正常。下面的Androd程序在主Activity中为button(button需要先添加到layout中)绑定了一个监听器,用于调用Hello类中的JNI方法,点击button出现4就说明调用成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private Button button;	

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* test JNI */
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 在此处添加逻辑
Hello test = new Hello();
Toast.makeText(MainActivity.this, "" + (test.testJni(2)), Toast.LENGTH_SHORT).show();
}
});
}

参考链接: