JNI基本使用二

Posted zhujm320

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JNI基本使用二相关的知识,希望对你有一定的参考价值。

介绍

      接着上一篇博文本次主要介绍JNI的动态注册,以及在JNI中如何开线程。JNI的基础部分,请详细参考JNI的基本使用一

动态注册

     与静态注册相比,有如下优缺点:

     优点:

       1. native中函数名简洁, 方便记忆

       2. 需要使用时,需要在 JNI_OnLoad() 进行注册

     缺点:

       1. native方法需要手动进行注册

工程中使用

     对上一篇博文的工程进行改造,将静态注册改成动态注册。在JNI_OnLoad中采用 RegisterNatives 对JNI函数进行注册。

 JNINativeMethod 说明

 JNINativeMethod 动态注册使用的结构体,描述了函数的参数和返回值 。

typedef struct 
    char *name;
    char *signature;
    void *fnPtr;
 JNINativeMethod;

第一个变量name是Java中函数的名字。

第二个变量signature,用字符串是描述了函数的参数和返回值。

第三个变量fnPtr是函数指针,指向C函数。

动态注册步骤

1. 将java中的native函数名和jni中的实现通过进行一一绑定,填充JNINativeMethod结构体

2. 通过env->RegisterNatives进行注册

3. 在JNI_OnLoad中调用注册函数

代码如下:

填充JNINativeMethod结构体

//java文件路径
static const char *classPathName = "com/stx/jni/JNITest";
static JNINativeMethod methods[] = 
	 "javaCallNative", "()V", (void *)(javaCallNative) ,
	 "javaCallNative1", "(I)V", (void *)(javaCallNative1) ,
	 "javaCallNative2", "(J)V", (void *)(javaCallNative2) ,
	 "javaCallNative3", "(F)V", (void *)(javaCallNative3) ,
	 "javaCallNative4", "([B)V", (void *)(javaCallNative4) ,
	 "javaCallNative5", "(Ljava/lang/String;)V", (void *)(javaCallNative5) ,
	 "nativeCallJavaTest", "()V", (void *)(nativeCallJavaTest) 
;

实现注册函数

int registerNativeMethods(JNIEnv* env, const char* className,
	JNINativeMethod* gMethods, int numMethods) 
	jclass clazz;
	clazz = env->FindClass(className);
	if (clazz == NULL)
		return JNI_FALSE;

	if (env->RegisterNatives(clazz, gMethods, numMethods) < 0)
	
		printf("register nativers error\\n");
		return JNI_FALSE;
	
	return JNI_TRUE;


int registerNatives(JNIEnv* env) 
	if (!registerNativeMethods(env, classPathName,
		methods, sizeof(methods) / sizeof(methods[0]))) 
		return JNI_FALSE;
	
	return JNI_TRUE;

在JNI_OnLoad中进行调用

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) 
	printf("JNI_OnLoad....\\n");
	JNIEnv* env = NULL;
	if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
		return -1;
	if (env == NULL) 
		return -2;
	
	int result = registerNatives(env);
	if (result == 1)
	
		printf("JNI Register success...\\n");
	
	else 
		printf("JNI Register faild...\\n");
	
	return JNI_VERSION_1_4;

在JNI中启动线程

如何创建线程

JNI中开启线程, 需要根据不同的操作系统,然后根据操作系统的API进行创建线程。下面以windows操作系统为例子。

windows 创建线程API

HANDLE
WINAPI
CreateThread(
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ SIZE_T dwStackSize,
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    _In_opt_ __drv_aliasesMem LPVOID lpParameter,
    _In_ DWORD dwCreationFlags,
    _Out_opt_ LPDWORD lpThreadId
    );

参数说明:

第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。

第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

第四个参数 lpParameter 是传给线程函数的参数。

第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。

第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号

返回值: 返回线程句柄,NULL失败, 其他成功

创建线程代码:

void createJniThread(JNIEnv *env, jobject jObj) 
	printf("createJniThread---->\\n");
	threadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThread, NULL, 0, &threadid);

如何在线程中获取JNIEnv

JNIEnv 是和线程有关的变量,JVM是和进程有关的变量。所以不同的线程,都有不同的JNIEnv对象,那么在新的线程中需要通过JVM来重新获取JNIEnv对象。 注意: 需要在JNI_OnLoad中将JVM变量保存下来,然后在其他线程中使用。

获取JNIEnv方法

jint AttachCurrentThread(void **penv, void *args);

释放资源

jint DetachCurrentThread()

代码如下:

JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) 
	printf("JNI_OnLoad....\\n");
	JNIEnv* env = NULL;
	if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK)
		return -1;
	if (env == NULL) 
		return -2;
	
	int result = registerNatives(env);
	if (result == 1)
	
		printf("JNI Register success...\\n");
	
	else 
		printf("JNI Register faild...\\n");
	
	pJVM = vm;	//保存JVM结构体
	return JNI_VERSION_1_4;
/*
*
*线程执行函数
*/
void MyThread() 
	printf("this is new thread begin, thread id: %d \\n", threadid);
	for (int i = 100; i < 110; i++) 
		printf("%d\\n", i);
	
	if (pJVM != NULL) 
		printf("pJVM is not null, begin get jnienv...\\n");
		JNIEnv* pEnv = NULL;
		pJVM->AttachCurrentThread((void**)&pEnv, NULL);
		if (pEnv != NULL) 
			printf("get jinenv success...\\n");
		else 
			printf("get jinenv faild...\\n");
		
		pJVM->DetachCurrentThread();
	
	else 
		printf("pJVM is null...\\n");
	
	printf("this is new thread end, thread id: %d \\n", threadid);

测试代码

     在java中调用jni函数,并开启jni线程

package com.stx.jni;

import java.io.File;

public class JNITest 

    static 
        System.loadLibrary("JNIDll");
    

    public static void main(String[] args)  
        System.out.println("==================");
        File file = new File("");
        System.out.println("curDir " + file.getAbsolutePath());
        JNITest jniTest = new JNITest();
        jniTest.javaCallNative();

        jniTest.javaCallNative1(1);
        jniTest.javaCallNative2(2);
        jniTest.javaCallNative3(3.0f);
        byte[] byteTest = new byte[]1, 2, 3, 4, 5, 6;
        jniTest.javaCallNative4(byteTest);
        String strTest = "Hello World";
        jniTest.javaCallNative5(strTest);
        jniTest.nativeCallJavaTest();
        jniTest.createJniThread();
        try 
            Thread.sleep(500);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    

    public native void javaCallNative();

    public native void javaCallNative1(int arg);
    public native void javaCallNative2(long arg);
    public native void javaCallNative3(float arg);
    public native void javaCallNative4(byte[] arg);
    public native void javaCallNative5(String arg);
    /**
     * 通过该接口c层回调java接口
     */
    public native void nativeCallJavaTest();

    public native void createJniThread();

    /**
     * 提供给JNI调用
     * @param arg1
     * @param arg2
     * @param arg3
     */
    public void test(int arg1, byte[] arg2, String arg3)
        System.out.println("this is java test print...");
        System.out.println("arg1: " + arg1);
        System.out.println("================arg2 begin=============");
        for (byte b : arg2)
            System.out.println(""+b);
        
        System.out.println("================arg2 end=============");
        System.out.println("arg3: " + arg3);
    


测试结果

com.stx.jni.JNITest
==================
curDir D:\\Project\\java\\JNIDemo
this is java test print...
arg1: 10
================arg2 begin=============
0
1
2
3
4
5
================arg2 end=============
arg3: C++ Char
JNI_OnLoad....
JNI Register success...
javaCallNative---->
javaCallNative1---->, arg: 1
javaCallNative2---->, arg: 2
javaCallNative3---->, arg: 3.000000
javaCallNative4---->
1
2
3
4
5
6
javaCallNative5---->
csArgs: Hello World
nativeCallJavaTest---->
createJniThread---->
this is new thread begin, thread id: 13736 
100
101
102
103
104
105
106
107
108
109
pJVM is not null, begin get jnienv...
get jinenv success...
this is new thread end, thread id: 13736 

 源码下载

 

以上是关于JNI基本使用二的主要内容,如果未能解决你的问题,请参考以下文章

JNI基本使用二

如何在 JNI 中将 char[] 转换为 ByteBuffer?

在YUV_420_888中将图像从Android发送到OpenCV Mat中的JNI的最有效方式

NDK:JNI 的数据结构

JNI的基本使用一

JNI的基本使用一