最近在做的项目考虑调用WindowsAPI实现对程序内存的监控
而调用的接口是C/C++的 所以去学习了JNI的相关知识与基本的操作内容 最终还是很艰难的实现了想要实现的功能
把学习JNI的过程大致记录了下来 包含初步的基础操作与相互传递数据的操作
没有去深入学习原理 仅仅是“实现”了需要的功能 不足之处欢迎指正~
第一步:编写一个测试类
package com.tzy.test;
public class JNIDemo1
{
public native void JNITest1();
public static void main(String[] args)
{
System.loadLibrary("TestJNI1");
JNIDemo1 jniDemo1=new JNIDemo1();
jniDemo1.JNITest1();
}
}
这段代码中,JNITest1 为在Java程序中声明要在C/C++代码中实现的函数
TestJNI1则为最终与本Java程序实现连接的.c/.cpp名 之后在windows下会生成.dll后缀的同名文件,需要将这个文件路径导入到path中,在之后的步骤会详细说明
第二步:使用javah指令生成头文件
在该Java项目的bin目录下使用以下指令:
javah -classpath . -jni com.tzy.test.JNIDemo1
将生成一个头文件:
第三步:编写C/C++代码
我使用的是Visual Studio2015 编写的是C++代码
项目名称与Java代码中loadLibrary中的名字对应
勾选应用程序类型为DLL 附加选项空项目
项目建立完成在源文件中添加类:
接下来将
1.第二步中生成的头文件
2.JDK/include下的jni.h
3.JDK/include/win32下的jni_md.h
拷贝至C++项目的工程目录下
VS生成的工程目录TestJNI1中还有一个TestJNI1 拷贝至里面的那个
拷贝完成后在头文件中添加现有项
选中刚才拷贝的三个头文件
打开第二步中生成的头文件 把报错的#include<jni.h>改为#include "jni.h" 表示引用外部头文件
接下来编写TestJNI.cpp,将刚才修改的头文件中的
实现即可
#include "TestJNI.h"
#include "com_tzy_test_JNIDemo1.h"
#include <iostream>
using namespace std;
JNIEXPORT void JNICALL Java_com_tzy_test_JNIDemo1_JNITest1
(JNIEnv *, jobject)
{
cout << "Hello World" << endl;
}
接着在解决方案上右键 ——属性
按以下方式修改
接着在项目上右键——生成
生成信息中包含目录,
复制该目录路径,在.dll上层
然后在Java项目中bulid path——Libraries——Native library location中添加
运行Java程序
成功输出
接下来进行传递数据的相应代码改变
其他部分操作不变,仅改变相应的Java与C++代码
首先进行一个简单的值的传递示范
package com.tzy.test;
public class JNIDemo1
{
public native int JNITest2(int chuanruzhi);
public static void main(String[] args)
{
System.loadLibrary("TestJNI2");
JNIDemo1 jniDemo1=new JNIDemo1();
int a=jniDemo1.JNITest2(10);
System.out.println(a);
}
}
在这段代码中,把native函数返回值从void改为int 同时传入一个int至C++
然后主函数使用这个函数,传入C++的值为10 然后输出从C++传回的值
在javah生成的头文件中可以看到
对比刚才的函数
发现1.返回值由void变为jint 参数列表中多了一个jint 这个就是Java参数中的int
两者没有实际区别
同样对于boolean byte char short long float double 在C++中加上j就可以了
不同的在于字符串与数组的传递较为复杂
接下来在TestJNI2.cpp中实现这个函数
#include "TestJNI2.h"
#include "com_tzy_test_JNIDemo1.h"
#include<iostream>
JNIEXPORT jint JNICALL Java_com_tzy_test_JNIDemo1_JNITest2
(JNIEnv *, jobject, jint input)
{
int output = input + 1;
return output;
}
这段代码接收Java传入的int 并把这个值加1之后返回给Java
然后同样生成dll文件 在java项目中添加dll文件的上层目录
运行
结果如下:
最后写一个字符串传递的例子
package com.tzy.test;
public class JNIDemo1
{
public native String JNITest3(String chuanruzhi);
public static void main(String[] args)
{
System.loadLibrary("TestJNI3");
JNIDemo1 jniDemo1=new JNIDemo1();
String a=jniDemo1.JNITest3("Hello World from Java");
System.out.println(a);
}
}
在这段代码中,native函数返回值改为String 同时传入一个String给C++
javah生成的头文件中 函数为如下:
可以看到string变为了jstring 但是这个jstring与jint不同 jint可以直接当做C++中的int使用 而jstring却不能直接当做C++中的string使用
在cpp中进行以下处理:
#include "TestJNI3.h"
#include "com_tzy_test_JNIDemo1.h"
#include<iostream>
JNIEXPORT jstring JNICALL Java_com_tzy_test_JNIDemo1_JNITest3
(JNIEnv *env, jobject, jstring input)
{
const char* str;
str = env->GetStringUTFChars(input, false);
std::cout << str << std::endl;
env->ReleaseStringUTFChars(input, str);
char* tmpstr = "Hello World from C++";
jstring output= env->NewStringUTF(tmpstr);
return output;
}
利用etStringUTFChars和NewStringUTF函数进行转换
这两个函数都是JNI API提供的函数
这样实现的功能是 利用C++输出Java传入的fromJava
再返回fromC++到Java中
生成 保存path
运行:
其他:
对于数组,会转换成 j<类型>Array,比如int[]对应的就是jintArray 利用JNI提供的GetIntArrayElements函数进行操作
具体请参照API
若想一次返回多个类型的值,可以使用Hashmap进行传递,object与jobject之间进行传递