本人最近所做的一个项目需要将C代码移植到Java平台运行。由于先前没有JNI的任何使用经验,花了近一个星期的时间学习,现将过程记录如下。
软件环境:NetBeans IDE 8.0.1 + jdk1.8.0_20 + Visual Studio 2010.
1)首选编写java类JNICode.java:
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package test.jnitest;
/**
*
* @author Ai_ViVi
*/
public class JNICode {
static{
System.loadLibrary("NativeTestCode"); // 加载dll文件,无需后缀名
}
public native void saySomeThing();
public native void passIntArray(int n, int[] a); //传递整型数组
public native ObjTest[] returnIntArray(String[] str); //返回自定义结构数组
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
//System.out.println(System.getProperty("java.library.path"));
System.out.println("hello world from Java");
// for test
JNICode obj = new JNICode();
obj.saySomeThing();
int[] a = {1, 2, 3};
obj.passIntArray(3,a);
String[] names = {"Dr.1", "Dr.2", "Dr.3", "Dr.4"};
ObjTest[] rObj = obj.returnIntArray(names);
for(int i=0; i<rObj.length; i++)
{
//System.out.println(rObj[i].id);
System.out.println(rObj[i].name);
}
}
}
2)测试javah是否可用:
3)利用javah将JNICode.java编译成test_jnitest_JNICode.h文件(注意路径):
test_jnitest_JNICode.h文件位于:
C:\Users\Sandon\Documents\NetBeansProjects\JNItest\build\classes\
/* DO NOT EDIT THIS FILE - it is machine generated */
#include "jni.h"
/* Header for class test_jnitest_JNICode */
#ifndef _Included_test_jnitest_JNICode
#define _Included_test_jnitest_JNICode
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: test_jnitest_JNICode
* Method: saySomeThing
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_test_jnitest_JNICode_saySomeThing
(JNIEnv *, jobject);
/*
* Class: test_jnitest_JNICode
* Method: passIntArray
* Signature: (I[I)V
*/
JNIEXPORT void JNICALL Java_test_jnitest_JNICode_passIntArray
(JNIEnv *, jobject, jint, jintArray);
/*
* Class: test_jnitest_JNICode
* Method: returnIntArray
* Signature: ()[Ltest/jnitest/ObjTest;
*/
JNIEXPORT jobjectArray JNICALL Java_test_jnitest_JNICode_returnIntArray
(JNIEnv *, jobject, jobjectArray);
#ifdef __cplusplus
}
#endif
#endif
4)然后创建一个C工程NativeTestCode:
4.1)新建win32项目;
4.2)将C:\ProgramFiles\Java\jdk1.8.0_20\include下jni.h,
C:\ProgramFiles\Java\jdk1.8.0_20\include\win32下的jni_md.h拷贝到工程文件夹下。
4.3)拷贝test_jnitest_JNICode.h到工程文件夹下,并导入工程,打开该文件,将#include <jni.h>修改成#include “jni.h”
4.4)创建test_jnitest_JNICode.cpp文件,成员函数实例化:
#include "stdafx.h"
#include "test_jnitest_JNICode.h"
JNIEXPORT void JNICALL Java_test_jnitest_JNICode_saySomeThing
(JNIEnv *env, jobject jo)
{
printf("hello world from C\n");
}
JNIEXPORT void JNICALL Java_test_jnitest_JNICode_passIntArray
(JNIEnv *env, jobject jo, jint jn, jintArray jarr)
{
int num = jn;
jint* aar =env->GetIntArrayElements(jarr,0);
int arrLen=env->GetArrayLength(jarr);
if (num == arrLen)
{
printf("array length is right\n");
}
for (int i=0; i<arrLen; i++)
{
printf("%d ", aar[i]);
}
printf("\n");
delete aar;
}
JNIEXPORT jobjectArray JNICALL Java_test_jnitest_JNICode_returnIntArray
(JNIEnv *env, jobject jo, jobjectArray jstr)
{
jclass cls_obj = env->FindClass("test/jnitest/ObjTest"); //获取结构对象
jmethodID m = env->GetMethodID(cls_obj,"<init>","()V"); //初始化构造函数
jfieldID fid_id = env->GetFieldID(cls_obj, "id", "I"); //获取成员变量
jfieldID fid_name = env->GetFieldID(cls_obj, "name", "Ljava/lang/String;"); //获取成员变量
//------------将 char* 转化为 jstring
/* char* col_timestamp= "goodluck";
//加载string类
jclass strClass = env->FindClass("Ljava/lang/String;");
//获得方法id
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
//将字符串转换为jstring
jbyteArray bytes_time = env->NewByteArray(strlen(col_timestamp));
env->SetByteArrayRegion(bytes_time, 0, strlen(col_timestamp), (jbyte*)col_timestamp);
jstring js_time = env->NewStringUTF("utf-8");
js_time=(jstring)env->NewObject(strClass, ctorID, bytes_time, js_time); */
//------------将 jstring转化为char*
int np = env->GetArrayLength(jstr);
jclass cls_array=env->FindClass("java/lang/Object"); //创建结构数组
jobjectArray obj_array=env->NewObjectArray(np, cls_array, 0);
for (int i=0; i< np; i++)
{
jobject obj=env->NewObject(cls_obj,m);
int a = i;
env->SetIntField(obj, fid_id, a);
jobject str = env->GetObjectArrayElement(jstr,i);
env->SetObjectField(obj, fid_name, str);
env->SetObjectArrayElement(obj_array, i, obj);
}
return obj_array;
}
5)编译NativeTestCode项目生产NativeTestCode.dll文件
6)拷贝NativeTestCode.dll到java\jdk\bin下(假设该路径位于java.library.path下,可以使用System.out.println(System.getProperty("java.library.path"))进行查看),运行JNICode.java文件,结果如下:
另外:
1)如果C工程所生成的dll文件使用了其他dependent libraies(如OpenCV等),则需要将这些dll文件一并放入java.library.path下。
2)如果C工程中使用到了文件读写输入输出等,一定要用绝对路径,否则java调dll会出错。
3)很烦人的一点:一旦C的dll包含隐形错误(能编译通过),java主程序将错误退出,几乎不提供有用的错误信息。
参考:
NetBeans IDE Java快速入门教程:
https://netbeans.org/kb/docs/java/quickstart_zh_CN.html#setup
Java利用JNI调用C/C++写成的DLL(教程):
http://blog.youkuaiyun.com/tank_abraham/article/details/32071621
Java向C传入默认类型数组:
http://www.oschina.net/question/214234_162742
java和C之间的数据传递:
http://blog.chinaunix.net/uid-23023613-id-2566756.html
Java向C传递自定义对象的方法:
http://blog.jeoygin.org/2011/12/jni-pass-on-object-parameter.html
Java向C传递对象,中文字符串的方法:
http://lifeixian824.blog.163.com/blog/static/2837428520091222638738/
JNI中参数的传递与操作(分上中下三篇,详细介绍了一些API函数):
http://blog.youkuaiyun.com/hudashi/article/details/7058982
C向java返回对象的方法:
http://leidiqiu.iteye.com/blog/720307