JNI详解
Posted 一只努力的菜鸟。
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JNI详解相关的知识,希望对你有一定的参考价值。
JNI简介
JNI全名java native interface,相当于java和C中间的桥梁作用,一种协议.通过JNI就可以让java调用C语言或者C++代码,并且可以让C调用java代码.
如下图所示
安卓系统的结构图如下:
JNI_C的第一个程序
软件DEV
调用起java字节码,可以创建一个.java文件进行编译生成class文件,配置Class的环境变量,通过system(“java 文件名”)调用,此方法没有用到JNI.(了解)
JNI_C语言的基本类型
回顾java的8大基本类型:
byte:占用1个字节
short:占用2个字节
int:占用4个字节
char:占用2个字节
float:占用4个字节
double:占用8个字节
boolean:占用1个字节
long:占用8个字节
C语言如下:
输出为:
JNI_输出函数
printf("你要输出的内容")
%d - int
%ld - long int
%c - char
%f - float
%u - 无符号数
%hd - 短整型
%lf - double
%x - 十六进制输出
%0 - 八进制输出
%s - 字符串
输出结果:
JNI_输入函数
Scanf(“占位符”,内存地址);
输出结果:
JNI_指针
输出结果:
指针和指针变量的关系:
指针就是地址,地址就是指针
地址就是内存单元的编号
指针变量是存放地址的变量
指针和指针变量是两个不同的概念
但是要注意:通常我们叙述时会把指针变量简称为指针,但是它们的实际含义不一样
指针里面存放的100, 这个时候讲的指针是一个地址而且是一个具体的地址
指针里面存放的地址, 这个时候指针是一个指针变量是可变的(地址可能指向不同的东西)
为什么要用指针?
指针可以直接访问硬件比如显卡的绘图
(很多驱动居于C开发的原因就是C可以直接驱动硬件)
快速传递数据,指针表示地址
返回一个以上的值(返回一个数组或者结构体的指针)
表示复杂的数据结构(结构体)
方便处理字符串
指针有助于理解面向对象
*的三种含义
数学运算符:3*5
定义指针变量:int* p;
指针运算符(取值):*p(取p的地址在内存中的值)
JNI_C互换两个数字
传统方法:
输出结果:
传送过去之后地址不相同导致了数值没有变化
正确方法如下:
输出结果如下成功转换值:
JNI_函数返回一个以上的值
输出结果:
在main方法里面直接调用了close并没有返回值,但是返回了两个值,这两个值进行了改变,原因就是指针
通过被调函数修改主调函数普通变量的值
- 实参必须是普通变量的地址
- 形参必须是指针变量
- 被调函数中通过 *形参名的方式修改主调函数相关变量的值
JNI_多级指针
输出结果:
解析: *p是存放地址对应的值.那么*address = &address3 也就是address3地址对应的值
**address4 就是 &&address3就是address3地址对应的值,也就是&address2
***address4就是&address1 ****address4就是 &i也就是100;
JNI_数组
输出结果为:
*iArray + 0 :数组是一个连续的内存空间,内存空间就是地址.加上下一个下标就跳到了下一个
*(iArray + 0):加上坐标再去取相当于根据下标进行取值,这种是正规的做法
&iArray:数组的地址和数组的首地址相同
&iArray再数组中等同于iArray
JNI_输入数组
输出结果:
JNI_静态内存分配
输出结果:
解析:静态内存是程序编译后系统(栈)分配的,由系统自动释放, 所以当第一个执行完之后对func()进行回收再去进行取值没了.
JNI_动态内存分配
输出结果:
解析:temp = malloc(sizeif(int))返回的是一个长度为4的内存地址,相当于在堆区里面又申请了一块内存空间,存放的就是i的内存地址.当执行完func()后,fun()被回收,但是申请的还在,所以怎么取值都不会变.但是造成了资源没有回收.可以通过free(temp)回收掉手动申请的temp内存空间.
动态内存和静态内存
静态内存是程序编译执行后系统自动分配,由系统自动释放,静态内存是栈分配的
动态内存是开发者手动分配的,是堆分配的
- 从静态内存分配.内存在程序编译的时候已经分配好,这块内存在程序的整个运行期间都存在.例如全局变量,static变量,
- 从栈上创建.在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限
- 从堆上分配,也称为动态 内存分配.程序运行时候用malloc或new申请任意多的内存,程序员自己负责在何时用free和delete进行释放.动态分配的生存期间由我们决定,使用非常灵活但是问题也多.
JNI_动态创建数组
输出结果:
解析:maclloc()动态分配内存空间,realloc是重新分配空间
JNI_函数指针
输出结果:
JNI_C联合体
输出结果:
JNI_枚举
输出结果:
JNI_结构体
别名:typeof
输出结果:
一般函数名特别长使用别名代替,这种情况下才会使用.平时使用较少
结构体
输出结果:
JNI_结构体指针
输出结果
JNI_Android Studio下的NDK环境配置
- 解压NDK的zip安装包到非中文目录
- 配置path:解压后的NDK根目录,复制解压后的根目录到环境变量下面的path中加入,然后执行ndk-build提示如下则配置成功
JNI_快速开发
- 创建一个project,首先写JAVA代码,如下
2.创建JNI.java调用C的代码
3.在JAVA同级目录下创建jni目录
4.在jni目录下面写.C文件
注:String类型的返回值就是jstring其他的如下所示,后面的是包名类名前面加上Java,NewStringUTF可在jni.h(就在下载的ndk中)文件中查询:
5.在项目目录(main)下面执行ndk-build,提示如下.发现需要创建android.mk
6.在jni目录下面创建Android.mk如下图所示
7.再次执行ndk-build生成.so文件如下所示,目录有指向
8.查看生成的.so文件,前面带lib是正确的,之后引用.so文件进行JAVA调用C(要注意System引包是long)
9.提示dlopen failed: library "libhello.so" not found 没有找到这个.so文件,在build.gradle里面进行配置如下(与buildtypes同级):
最终输出结果:
头信息可以简便生成.进入app/src/main目录下,执行:
javah -d jni -classpath ./java com.example.jnijianbian.MainActivity
如下,之后在android studio项目的jni目录下就能看见.h的头文件
.mk文件信息如下
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES := hello.c
include $(BUILD_SHARED_LIBRARY)
.gradle信息如下:
sourceSets
main
jniLibs.srcDirs = ['src/main/libs','libs']
JNI_开发中常见错误
一 本地方法没有找到
1.当java调用C的native方法名中有_,那么在C实现中也会有_就会很迷惑,不清楚是方法名还是类名
当重新执行ndk-build后,运行程序崩溃,提示Native method not found
解决:在C函数名里面前面加个1 比如 say_hello -> say_1hello,当然特多别的1时候可以通过javah生成方法名,自动生成一个名字.如果jdk1.7以上到项目的src目录下面运行javah javah要生成C函数名字的java全路径(就是native方法的全路径)
2.忘记写System.loadLibrary() 可以通过静态代码块加载.so文件
二 找lib的时候(.so文件)返回null
1..so文件名字写错 lib前缀去掉, .so后缀去掉 剩下的就是要加载文件的名字
2.当前的.so文件不被cpu平台支持,需要通过在jni目录下添加Application.mk来指定编译后.so文件支持的cpu平台 例如 APP_ABI := armeabi x86
JNI_JAVA传递给C String类型数据
当执行javah -d jni -classpath ./java com.example.intdatasend.MainActivity时候会提示如下:
解决办法:AndroidStudio右下角选择编码格式为GBK编码如下所示(或者用notepad++改变编码为ANSI)
C语言中是没有String类型的,所以需要char类型进行接收转换如下方法:
char* _JString2CStr(JNIEnv* env, jstring jstr)
char* rtn = NULL;
jclass clsstring = (*env)->FindClass(env, "java/lang/String");
jstring strencode = (*env)->NewStringUTF(env,"GB2312");
jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
jsize alen = (*env)->GetArrayLength(env, barr);
jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
if(alen > 0)
rtn = (char*)malloc(alen+1); //"\\0"
memcpy(rtn, ba, alen);
rtn[alen]=0;
(*env)->ReleaseByteArrayElements(env, barr, ba,0);
return rtn;
之后进行返回
JNI_java传递int类型数组数据给C
java中定义如下:
int[] arr = new int[]1, 2, 3;
int result[] = getIntArray(arr);
for (int i : result)
System.out.println("i =" + i);
public native int[] getIntArray(int[] arr);
//C返回如下
JNIEXPORT jintArray JNICALL Java_com_example_intdatasend_MainActivity_getIntArray
(JNIEnv * env, jobject thiz, jintArray jarr)
//获取数组的长度
int length = (*env)->GetArrayLength(env,jarr);
//获取数组首地址
int* p = (*env)->GetIntArrayElements(env,jarr,0);
int i;
for(i = 0; i<length;i++)
*(p+i) +=10;
//将C数组中的元素拷贝到Java数组中
(*env)->SetIntArrayRegion(env,jarr,0,length,p);
return jarr;
输出结果:
JNI_向控制台输出日志
首先在jni目录下面的Android.mk添加一句
LOCAL_LDLIBS += -llog
之后再C的头文件添加如下
#include <android/log.h> #define LOG_TAG "memorytester" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
再执行ndk-build就可以打印日志了,调用方法如下:
LOGD("length=%d",length);
输出结果:
JNI_反射回调JAVA方法
首先回顾Java的反射调用如下:
JNI的调用:
首先定义Java调用C的方法以及C回调的方法如下:
public class JNI
public void helloFromJava()
System.out.println("我来自java");
public int add(int x, int y)
return x + y;
public void printString(String s)
System.out.println(s);
public native void callBackVoid();
public native void callBackInt();
public native void callBackString();
方法签名的生成:
编译后cd到如下目录
执行javap -s com/example/intdatasend/JNI (想声明方法签名的全类名)如下:
之后在JNI目录下创建Android.mk进行编写,在grade里面设置路径,通过命令生成方法名,之后C代码实现如下
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#define LOG_TAG "memorytester"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
JNIEXPORT void JNICALL Java_com_example_intdatasend_JNI_callBackVoid
(JNIEnv * env, jobject thiz)
//1.找到字节码
jclass clazz = (*env)->FindClass(env,"com/example/intdatasend/JNI");
//2.找到方法 ()V方法签名 void方法
jmethodID methodID = (*env)->GetMethodID(env,clazz,"helloFromJava","()V");
//3.创建对象(可选)
//4.通过对象调用方法
(*env)->CallVoidMethod(env,thiz,methodID);
JNIEXPORT void JNICALL Java_com_example_intdatasend_JNI_callBackInt
(JNIEnv * env, jobject thiz)
//1.找到字节码
jclass clazz = (*env)->FindClass(env,"com/example/intdatasend/JNI");
//2.找到方法 (II)I表示两个int类型参数并且返回int
jmethodID methodID = (*env)->GetMethodID(env,clazz,"add","(II)I");
//4.通过对象调用方法
int result = (*env)->CallIntMethod(env,thiz,methodID,3,4);
LOGD("result=%d",result);
JNIEXPORT void JNICALL Java_com_example_intdatasend_JNI_callBackString
(JNIEnv * env, jobject thiz)
//1.找到字节码
jclass clazz = (*env)->FindClass(env,"com/example/intdatasend/JNI");
//2.找到方法 (II)I表示两个int类型参数并且返回int
jmethodID methodID = (*env)->GetMethodID(env,clazz,"printString","(Ljava/lang/String;)V");
//4.通过对象调用方法
jstring jstr = (*env)->NewStringUTF(env,"hello");
(*env)->CallVoidMethod(env,thiz,methodID,jstr);
输出结果:
Java中JNI的使用详解第一篇:HelloWorld
转自: http://blog.csdn.net/jiangwei0910410003/article/details/17465085
今天开始研究JNI技术,首先还是老套路,输出一个HelloWorld:具体流程如下:在Java中定义一个方法,在C++中实现这个方法,在方法内部输出“Hello World",然后再回到Java中进行调用。分为以下步骤:
第一步:在Eclipse中建立一个类:JNIDemo
- package com.jni.demo;
- public class JNIDemo {
- //定义一个本地方法
- public native void sayHello();
- public static void main(String[] args){
- //调用动态链接库
- System.loadLibrary("JNIDemo");
- JNIDemo jniDemo = new JNIDemo();
- jniDemo.sayHello();
- }
- }
第二步:使用javah命令将JNIDemo生成.h的头文件:
命令如下:
E:\workspace\JNIDemo\bin>javah com.jni.demo.JNIDemo
注意:
1. 首先要确保配置了Java的环境变量的配置,不然javah命令不能用,具体怎么配置见:http://blog.csdn.net/jiangwei0910410003/article/details/17463173
2. 我的Java项目是放在E:\workspace中的,所以首先进入到工程的bin目录中,然后使用javah命令生成头文件
3. javah后面的类文件的格式:是类的全名(包名+class文件名),同时不能有.class后缀
命令执行成功后会在bin目录中生成头文件:com_jni_demo_JNIDemo.h
但是我们还需要注意一个问题,就是如果我们的包含native方法的类,如果引用其他地方的类,那么这时候进入bin\classes\目录下会出现问题提示找不到指定的类,这时候我们需要切换到源码目录src下运行即可。
第三步:使用VC6.0生成.dll文件:
首先创建一个dll工程:
在.cpp文件中输入如下代码:
- <span style="font-size:14px;">#include<iostream.h>
- #include "com_jni_demo_JNIDemo.h"
- JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
- {
- cout<<"Hello World"<<endl;
- }</span>
说明:
1. JNIEXPORT void JNICALL Java_com_jni_demo_JNIDemo_sayHello (JNIEnv * env, jobject obj)
{
cout<<"Hello World"<<endl;
}
这个方法的声明可以在上面生成的com_jni_demo_JNIDemo.h头文件中找到,这个就是Java工程中的sayHello方法的实现
2. 这里编译会出现几个问题:
(1):会提示你找不到相应的头文件:
这时候需要将jni.h,jni_md.h文件考到工程目录中,这两个文件的具体位置在:
java的安装目录中的include文件夹下,jni_md.h这个文件在win32文件夹中,找到这两个文件后,将其拷贝到C++的工程目录中;
(2) 当拷贝到这两个文件之后,编译还是提示找不到这两个文件:主要原因就是#include<jni.h>这个是从系统目录中查找jni.h头文件的,而我们只把jni.h拷贝到工程目录中,所以需要在com_jni_demo_JNIDemo.h头文件中将#include<jni.h>改成#include "jni.h",同理在jni.h文件中将#include<jni_md.h>改成#include "jni_md.h"
(3) 同时还有一个错误就是,提示:e:\c++\jnidemo\jnidemo.cpp(9) : fatal error C1010: unexpected end of file while looking for precompiled header directive,这个是预编译头文件读写错误,这时候还要在VC中进行设置:项目-》设置-》C/C++;在分类中选择预编译头文件,选择不使用预补偿页眉:
这样,编译成功,生成JNIDemo.dll文件在C++工程中的Debug目录中
第四步:将JNIDemo.dll文件添加到path环境变量中:
注意:在用户变量中的path设置,用分号隔开: ” ;E:\C++\Debug“,这样就将.dll文件添加到环境变量中了
第五步:在Eclipse中调用sayHello方法输出"Hello World":代码如下:
- public static void main(String[] args){
- //调用动态链接库
- System.loadLibrary("JNIDemo");
- JNIDemo jniDemo = new JNIDemo();
- jniDemo.sayHello();
- }
System.loadLibrary方法就是加载JNIDemo.dll文件的,一定要注意不要有.dll后缀名,只需要文件名即可;
注意:运行的时候会报错:
这个提示就是没有找到JNIDemo.dll文件,这时候我们需要关闭Eclipse,然后在打开,运行就没有错了,原因是Eclipse每次打开的时候都会去读取环境变量的配置,我们刚才配置的path,没有立即生效,所以要关闭Eclipse,然后从新打开一次即可。
至此,一个简单的JNI例子到此演示完毕,后续还有更高级的应用,要及时关注呀!