设为首页 收藏本站
查看: 1097|回复: 0

[经验分享] Mac及Android环境下的JNI学习

[复制链接]

尚未签到

发表于 2016-5-18 11:04:26 | 显示全部楼层 |阅读模式

Mac及Android环境下的JNI学习


4 人阅读
发表回复




  • 简介

  • 从Java中调用C/C++库


    • Hello World

    • 带参数的函数

    • Android中的情况


  • 从C/C++中调用JAVA

  • 高级主题

  • 参考




简介



JNI就是Java Native Interface, 也可以理解为一般脚本语言的C API, 一般情况下这种API的学习都是一种痛苦的精力, 从来如此, 没有太多技术含量, 就是一堆晦涩难以理解的编程模型, 编程接口, 充斥着各种从当前语言到C语言的类型转换. 基本的含义就是用C语言的思维去表示当前的语言, 这个问题在Lua语言中到达了极致. 不管是多么为了效率, 一个纯堆栈操作的编程接口都像汇编语言一样难以使用.

因为最近又开始做Android游戏了, 用的是cocos2d-x, JNI是难以避免了, 以前的使用都是照猫画虎似的写几个函数调用接口, 总感觉有问题, 今天好好的学习学习吧.

与一般关于JNI文章稍微有些不一样的是, 本文会更多的关注于Android相关的问题.

本文使用的环境是:

Mac OS X 10.8.3

java version “1.6.0_43″

Java(TM) SE Runtime Environment (build 1.6.0_43-b01-447-11M4203)

Java HotSpot(TM) 64-Bit Server VM (build 20.14-b01-447, mixed mode)

gcc version 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)

Android SDK API 17

Android ndk r8d


从Java中调用C/C++库



从Java中调用C/C++库的典型使用场景就是在Android中Load自己写的游戏库, 然后运行. 虽然有cocos2d-x引擎的时候你几乎不用关心这个.


Hello World



从R&D开始, Hello World就成了一开始必用的例子了, 学习JNI也从这个开始吧.

首先构建一个最简单的Java class:

// HelloWorld.java
import java.lang.System.*;
public class HelloWorld {
public native void SayHelloWorld();
public static void main(String[] args) {
System.loadLibrary("helloworld");
HelloWorld helloworld = new HelloWorld();
helloworld.SayHelloWorld();
}
}




native关键字表示的接口就是需要用C/C++来实现的接口, System.loadLibrary调用的就是将会实现的jni库.

然后用javac将文件编译成字节码:

javac HelloWorld.java




生成HelloWorld.class, java中最人性的一点就是把binding的生成直接作为标准了, 这一点比Python和lua要强多了, 也使得JNI的使用是我接触过的语言中, 类似C API最方便的一个. 直接生成C/C++ binding头文件的方式是用javah命令:

javah HelloWorld




从上面的HelloWorld类生成的头文件如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
   * Class:     HelloWorld
   * Method:    SayHelloWorld
   * Signature: ()V
   */
JNIEXPORT void JNICALL Java_HelloWorld_SayHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif




这实在是相当人性化了, 我们只需要包含HelloWorld.h头文件, 然后实现按照头文件给的签名实现Java_HelloWorld_SayHelloWorld函数就行了, 而不用自己去记住这么复杂的函数名和参数. 实现的HelloWorld.cc文件如下:

// HelloWorld.cc
#include "HelloWorld.h"
#include <cstdio>
JNIEXPORT void JNICALL Java_HelloWorld_SayHelloWorld(JNIEnv *, jobject) {
printf("HelloWorld.\n");
}




编译C++代码的时候在MacOS下和在Linux, Windows有所不同, 不是编译成.so或者dll, 而是MacOS自己的jnilib. 并且jni.h的目录也比较特殊, 是/System/Library/Frameworks/JavaVM.framework/Headers/,
这个需要稍微注意一下, 具体的命令如下:

g++ -dynamiclib -o libhelloworld.jnilib HelloWorld.cc -framework JavaVM -I/System/Library/Frameworks/JavaVM.framework/Headers




此时一切就绪:

>java HelloWorld
HelloWorld.




带参数的函数



两个语言之间的来回调用, 在没有任何参数的情况下还好, 有了参数以后, 因为牵涉到两个语言对类型的不同表示方式, 需要进行类型转换, 是最麻烦的地方, 比如在Lua, Python中, 使用C API时, 你就需要记住Lua和Python的各种类型分别对应C语言中的哪个类型, JAVA中在调用C/C++函数时, 在Java中通过javah部分缓解了这个问题, 可以让我们直接知道对应的类型是哪一个, 不过具体每个用C语言表示的JAVA类型该怎么用, 还是查文档吧, 比如下面这个例子:

// ArugmentTest.java
import java.lang.System.*;
public class ArgumentTest {
public native int intMethod(int n);
public native boolean booleanMethod(boolean bool);
public native String stringMethod(String text);
public native int intArrayMethod(int[] intArray);
public static void main(String[] args) {
System.loadLibrary("argumenttest");
ArgumentTest obj = new ArgumentTest();
System.out.println("intMethod: " + obj.intMethod(5));
System.out.println("booleanMethod: " + obj.booleanMethod(true));
System.out.println("stringMethod: " + obj.stringMethod("JAVA"));
System.out.println("intArrayMethod: " + obj.intArrayMethod(new int[]{1,2,3,4,5}));
}
}




编译后, 用javah生成的头文件如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class ArgumentTest */
#ifndef _Included_ArgumentTest
#define _Included_ArgumentTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     ArgumentTest
* Method:    intMethod
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_ArgumentTest_intMethod
(JNIEnv *, jobject, jint);
/*
* Class:     ArgumentTest
* Method:    booleanMethod
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL Java_ArgumentTest_booleanMethod
(JNIEnv *, jobject, jboolean);
/*
* Class:     ArgumentTest
* Method:    stringMethod
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_ArgumentTest_stringMethod
(JNIEnv *, jobject, jstring);
/*
* Class:     ArgumentTest
* Method:    intArrayMethod
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_ArgumentTest_intArrayMethod
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
}
#endif
#endif




这里我们可以看到, 大概的类型对应关系:

Object=>jobject

int=>jint

boolean=>jboolean

String=>jstring

int[]=>jintArray

但是, jobject, jstring, jintArray具体怎么使用, 总归得再学习一遍. 但是javah的存在还是非常有意义的, 起码我们不用记哪个类型该查什么文档了.

实现的C++文件如下:

// ArgumentTest.cc
#include "ArgumentTest.h"
#include <cstring>
using namespace std;
JNIEXPORT jint JNICALL Java_ArgumentTest_intMethod(JNIEnv *env, jobject obj, jint num) {
return num * num;
}
JNIEXPORT jboolean JNICALL Java_ArgumentTest_booleanMethod(JNIEnv *env, jobject obj, jboolean boolean) {
return !boolean;
}
JNIEXPORT jstring JNICALL Java_ArgumentTest_stringMethod(JNIEnv *env, jobject obj, jstring str) {
const char *cstr = env->GetStringUTFChars(str, 0);
char cap[128] = "language: ";
strcat(cap, cstr);
env->ReleaseStringUTFChars(str, cstr);
return env->NewStringUTF(cap);
}
JNIEXPORT jint JNICALL Java_ArgumentTest_intArrayMethod(JNIEnv *env, jobject obj, jintArray array) {
jsize len = env->GetArrayLength(array);
jint *body = env ->GetIntArrayElements(array, 0);
int sum = 0;
for (int i=0; i<len; ++i) {
sum += body[i];
}
env->ReleaseIntArrayElements(array, body, 0);
return sum;
}




这里可以看到几个特殊的函数, GetStringUTFChars, GetArrayLength, GetIntArrayElements, ReleaseIntArrayElements等, 还好都不算太复杂. 一旦用了JNI, 需要注意的就是, 你资源的分配释放, 就得和C/C++中一样了, 得自己手动来.

另外, 还值得一提的是, 因为C++对类的直接支持, 所以C++中可以用比C语言更简洁的语法, 大概的区别看了下面的示例:

C代码:(*env)->GetStringUTFChars(env, string, 0);

C++代码:env->GetStringUTFChars(string, 0);


Android中的情况



其实实现和使用方式都类似, 只是编译时, 需要使用不同的命令, 其实因为Android其实就是一种特殊的Linux, 所以对于Android来说, 生成方式和Linux类似, 并且都是生成Unix/Linux通用的.so动态库文件.

另外, 还有一些典型的Android的问题, 比如在Android中去完成前面的ArugmentTest:

import android.util.Log;
import android.app.Activity;
import android.view.View;
import android.os.Bundle;
public class ArgumentTest extends Activity
{
private static final String LOG_TAG = "ArugmenetTest";
public native int intMethod(int n);
public native boolean booleanMethod(boolean bool);
public native String stringMethod(String text);
public native int intArrayMethod(int[] intArray);
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
/* Create a TextView and set its content.
         * the text is retrieved by calling a native
         * function.
         */
Log.v(LOG_TAG, "begin.\n");
View v = new View(this);
setContentView(v);
Log.v(LOG_TAG, "intMethod: " + this.intMethod(5));
Log.v(LOG_TAG, "booleanMethod: " + this.booleanMethod(true));
Log.v(LOG_TAG, "stringMethod: " + this.stringMethod("JAVA"));
Log.v(LOG_TAG, "intArrayMethod: " + this.intArrayMethod(new int[]{1,2,3,4,5}));
}
static {
System.loadLibrary("argument_test");
}
}




需要注意的是, Android中就不要使用System.out这种标准JAVA的库输出了, 用的是android.util.Log这个再LogCat输出的类. 然后, 作为Android的工程, 一个Activity是必须的, 这里就把ArgumentTest做成了Activity了.

编译的时候需要注意, 一方面在Android中, 我们肯定需要指定一个自己的package, 不能像前面那边随意了, 这里的pakcage我用的是com.jtianling.ArgumentTest, 另外, 还需要指定一下android的classpath:

>javac -d ../jni/ com/jtianling/ArgumentTest/ArgumentTest.java -classpath ~/android/sdk/platforms/android-17/android.jar




同样的, pacakge在用javah的时候也必不可少, 不然就算生成了.so, 其实package对不上也是找不到的. 首先进入jni目录, 然后用下面的命令, 指定好package.

>javah com.jtianling.ArgumentTest.ArgumentTest




此时生成的头文件是带package信息的, 大概是下面这样子:
com_jtianling_ArgumentTest_ArgumentTest.h

文件的函数名也是类似, 也附带了package的信息:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jtianling_ArgumentTest_ArgumentTest */
#ifndef _Included_com_jtianling_ArgumentTest_ArgumentTest
#define _Included_com_jtianling_ArgumentTest_ArgumentTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     com_jtianling_ArgumentTest_ArgumentTest
* Method:    intMethod
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_jtianling_ArgumentTest_ArgumentTest_intMethod
(JNIEnv *, jobject, jint);
/*
* Class:     com_jtianling_ArgumentTest_ArgumentTest
* Method:    booleanMethod
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL Java_com_jtianling_ArgumentTest_ArgumentTest_booleanMethod
(JNIEnv *, jobject, jboolean);
/*
* Class:     com_jtianling_ArgumentTest_ArgumentTest
* Method:    stringMethod
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_jtianling_ArgumentTest_ArgumentTest_stringMethod
(JNIEnv *, jobject, jstring);
/*
* Class:     com_jtianling_ArgumentTest_ArgumentTest
* Method:    intArrayMethod
* Signature: ([I)I
*/
JNIEXPORT jint JNICALL Java_com_jtianling_ArgumentTest_ArgumentTest_intArrayMethod
(JNIEnv *, jobject, jintArray);
#ifdef __cplusplus
}
#endif
#endif




同样的, 按照签名和函数名实现即可, 在一个典型的Android工程中, jni相关的C/C++代码都是放在工程的jni目录下, 我们还需在此目录配置Android.mk文件, 大概如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := argument_test
LOCAL_SRC_FILES := argument_test.cc
include $(BUILD_SHARED_LIBRARY)




我们使用ndk-build来编译android的jni动态库. 编译的输入如下:

>ndk-build
Gdbserver      : [arm-linux-androideabi-4.6] libs/armeabi/gdbserver
Gdbsetup       : libs/armeabi/gdb.setup
Compile++ thumb  : argument_test <= argument_test.cc
StaticLibrary  : libstdc++.a
SharedLibrary  : libargument_test.so
Install        : libargument_test.so => libs/armeabi/libargument_test.so




看到最后的那句Install, 表示编译成功了.

此时, 在eclipse中运行, 就可以看到LogCat输出的结果:

V/ArugmenetTest(27895): begin.
V/ArugmenetTest(27895): intMethod: 25
V/ArugmenetTest(27895): booleanMethod: false
V/ArugmenetTest(27895): stringMethod: language: JAVA
V/ArugmenetTest(27895): intArrayMethod: 15




从C/C++中调用JAVA



因为在Android中我暂时还没有发现这种需求, 暂时就不太研究了, 稍微看了一下用 JNI 进行 Java 编程, 从 C/C++ 程序调用
Java 代码, 似乎和以前看到的在C/C++中使用v8 js引擎最像, 相当于直接用C/C++代码操作java的虚拟机, 来执行代码. 有兴趣的就直接去看原文吧.


高级主题



本文只是介绍了最基础的情况, 实际上情况没有那么简单, 两个语言之间(这里是java和C/C++)的互相调用是很麻烦的, 这里可以从参考1中的高级主题中,
看到一些例子.


参考




  • 用 JNI 进行 Java 编程

  • 使用JNI进行混合编程:在C/C++中调用Java代码

  • Java Development Guide
    for Mac

writenby九天雁翎(JTianLing) -- www.jtianling.com

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-218652-1-1.html 上篇帖子: 大批用户升级刚发布的美洲豹OS中遭遇数小时蓝屏停顿 下篇帖子: Ubuntu 10.04五步打造Mac OS X体验
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表