• Android游戏开发实践(1)之NDK与JNI开发01


    Android游戏开发实践(1)之NDK与JNI开发01

    NDK是Native Developement Kit的缩写,顾名思义,NDK是Google提供的一套原生Java代码与本地C/C++代码“交互”的开发工具集。而Android是运行在Dalvik虚拟机之上,支持通过JNI的方式调用本地C/C++动态链接库。C/C++有着较高的性能和移植性,通过这种调用机制就可以实现多平台开发、多语言混编的Android应用了。当然,这些都是基于JNI实现的。在游戏开发中,这种需求更是必不可少。

    作者:AlphaGL。版权所有,欢迎保留原文链接进行转载

    1、认识JNI

    JNI是Java Native Interface的缩写,也称为Java本地接口。是JVM规范中的一部分,因此,我们可以将任何实现了JVM规范的JNI程序在Java虚拟机中运行。这里的本地接口,主要指的是C/C++所现实的接口。因此,也使得我们可以通过这种方式重用C/C++开发的代码或模块。
    具体关于JNI的详细介绍,可以参见JNI的官方文档。

    Java Native Interface Specification:
    http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html

    2、JNI的类型和数据结构

    实现原生Java代码与本地C/C++代码,一个重要的环节是将原生Java的类型和数据结构映射成本地C/C++支持的相应的类型和数据结构。

    (1)Java基本数据类型与原生C/C++类型对应关系如下:

    Java类型 本地类型 说明
    boolean jboolean 无符号,8位
    byte jbyte 无符号,8位
    char jchar 无符号,16位
    short jshort 有符号,16位
    int jint 有符号,32位
    long jlong 有符号,64位
    float jfloat 32位
    double jdouble 64位
    void void N/A

    (2)Java引用数据类型与原生C/C++类型对应关系如下:

    Java类型 本地类型
    Object jobject
    Class jclass
    String jstring
    Object[] jobjectArray
    boolean[] jbooleanArray
    byte[] jbyteArray
    char[] jcharArray
    short[] jshortArray
    int[] jintArray
    long[] jlongArray
    float[] jfloatArray
    double[] jdoubleArray

    通过上面的对应关系可以发现,本地类型的命名基本上是在Java原生类型明明的前面加上了个j,组成j-type格式的新类型命名,还是很直观的。

    (3)JNI引用类型的类关系图,如下:

    (上图源自:Java Native Interface Specification文档)

    3、JNI函数的签名

    在函数的声明中,由函数的参数,返回值类型共同构成了函数的签名。因此,将Java函数映射到本地C/C++中的对应也要遵循相应的规则。

    (1)函数数据类型的签名关系如下:

    Java类型 类型签名
    boolean Z
    byte B
    char C
    short S
    int I
    long J
    float F
    double D
    void V
    full-qualified-class(全限定的类) L
    [] [
    boolean[] [Z
    byte[] [B
    char[] [C
    short[] [S
    int[] [I
    long[] [J
    float[] [F
    double[] [D

    注意:

    1. full-qualified-class(全限定的类):指的是引用类型,用L加全类名表示。
    2. 数组类型的签名,只取中括号左半边。

    (2)JNI函数签名格式比较
    Java函数原型:

       return-value fun(params1, params2, params3)
    

    return-value:表示返回值
    params:表示参数

    对应函数签名格式为:

       (params1params2params3)return-value
    

    注意:

    1. JNI函数签名中间都没逗号,没有空格
    2. 返回值在()后面
    3. 如果参数是引用类型,那么参数应该写为:L加全类名加分号。例如:Ljava/lang/String;

    根据这种规则,知道Java函数原型就能判断出对应的JNI函数的签名格式:

       // 原型为:
       boolean  isLoading();
       // 签名格式为:
       ()Z
    
       // 原型为:
       void  setLevel(int level);
       // 签名格式为:
       (I)V
    
       // 原型为:
       char  getCharFunc(int index, String str, int[] value);
       // 签名格式为:
       (ILjava/lang/String;[I)C
    

    4、JNI开发流程

    1.简要开发步骤

    JNI的具体开发流程总结起来分为这么几步:
    (1)在原生java类中声明native方法。native表明该方法为一个本地方法,由C/C++实现。
    (2)使用javac命令将带有声明native方法的类,编译成class字节码。javac是jdk自带的一个命令,一般在javapath/bin(javapath为java安装目录)路径下。
    (3)使用javah命令将编译好的class生成本地C/C++代码的.h头文件。同样,javah也是jdk自带的一个命令。
    (4)实现.h头文件中的方法。
    (5)将本地代码编译成动态库。注意,不同平台的动态库是不一样的。
    (6)在java工程中引用编译好的动态库。

    2.开发实例

    按照上面的开发步骤作为指导,来一步步实现个简单的JNI的例子。
    (1)新建名为HelloJNI的java工程,并新建一个声明了native方法的类。(这里就以Eclipse作为开发IDE举例了)

        package com.hellojni.test;
    
        public class HelloJni {
    
            public native void printJni();
            
            public static void main(String[] args) {
    
            }
        }
    

    (2)使用javac编译该HelloJni的类编译为.class文件。当然,这步也可以由Eclipse来完成即可。

    (3)使用javah命令生成头文件。在命令行终端下输入如下命令:

        javah -classpath E:\workplace\java\HelloJNI\src com.hellojni.test.HelloJni
    

    classpath:是指定加载类的路径
    com.hellojni.test.HelloJni:为完整类名。注意,不需要带java
    具体javah的使用参数介绍,可以输入javah -help

    如果,执行成功,会在当前目录下生成com_hellojni_test_HelloJni.h的头文件。

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

    可以看到javah自动为我们生成了一个Java_com_hellojni_test_HelloJni_printJni的方法。格式是:Java_Packagename_Classname_Methodname。
    首先,这里引入了jni.h的头文件。这个是jdk自带的一个头文件,一般在javapath/include(javapath为java安装目录)。

    (4)打开VS新建一个Win32控制台应用程序,应用程序类型选择DLL(Win平台动态库为.dll)。并将生成的Java_com_hellojni_test_HelloJni_printJni.h头文件拷贝到该工程目录下。
    然后,再将该头文件添加到工程中。如图:

    编译生成一下。会提示找不到jni.h。因此,把jni.h拷贝到工程目录下,并加入到项目中。jni.h一般在javapath/include(javapath为java安装目录)路径下。

    重新编译生成下,会提示找不到jni_md.h。这个文件在,javapath/include/win32路径下。拷贝该文件再加入工程。并修改Java_com_hellojni_test_HelloJni_printJni.h头文件。
    将#include <jni.h>修改为#include "jni.h",在当前目录下找jni.h头文件。

    新建一个hellojni.cpp的源文件。如下:

        #include "stdafx.h"
        #include <iostream>
        #include "com_hellojni_test_HelloJni.h"
    
        using namespace std;
    
        JNIEXPORT void JNICALL Java_com_hellojni_test_HelloJni_printJni(JNIEnv *env, jobject obj)
        {
            cout<<"Hello JNI"<<endl;
        }
    

    (5)再将工程重新生成下,成功的话,会在工程的Debug目录下生成一个HelloJni.dll的动态库。将HelloJni.dll所在的路径添加到环境变量,这样每次重新生成,在任意目录都能访问。

    (6)在java工程中引用刚生成的HelloJni.dll。并加入如下代码:

        package com.hellojni.test;
    
        public class HelloJni {
    
            public native void printJni();
            
            public static void main(String[] args) {
                System.loadLibrary("HelloJni");
                
                HelloJni hello = new HelloJni();
                hello.printJni();
            }
        }
    

    调用System.loadLibrary来加载动态库。注意,动态库的名字不需要加.dll

    运行java工程,这时候会提示Exception in thread “main” java.lang.UnsatisfiedLinkError: no HelloJni in java.library.path。这时候,需要重启下Eclipse。因为,刚配置的环境变量。重启下,Eclipse才能识别。

    重启完毕,运行java工程。控制台会输入:
    Hello JNI
    表明整个JNI调用成功。

    第一篇就介绍这么多,大体明白了JNI的整个开发流程及基本规则。下一篇将介绍下在Android NDK环境下的交叉编译及调用过程。

    技术交流QQ群:528655025
    作者:AlphaGL
    出处:http://www.cnblogs.com/alphagl/
    版权所有,欢迎保留原文链接进行转载

  • 相关阅读:
    php的cURL库介绍
    php函数ob_start()、ob_end_clean()、ob_get_contents()
    php中curl、fsockopen的应用
    App架构设计经验谈:服务端接口的设计
    图解正向代理与反向代理
    三种数据库连接池的配置
    数据库连接池在Tomcat中的几种配置方法
    Java四种线程池的使用
    JVM调优总结(一)-- 一些概念
    JVM调优总结(十)-调优方法
  • 原文地址:https://www.cnblogs.com/alphagl/p/6064013.html
Copyright © 2020-2023  润新知