最近在做的項目考慮調用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之間進行傳遞