JNI编程初试
首先编写Java类,用于调用C++库导出的方法:
public class TestMain
{
public native boolean printInfo();
public native int getNum();
public native void setNum(int num);
三个方法,第一个方法用于打印“hello world!”到屏幕。第二个方法用于获取一个全局变量的值,第三个方法用于设置全局变量的值。
找到eclipse输出的class文件的所在的包的位置,我的目录是在“F:\java_code\TestJNI\bin”,然后执行如下命令:
找到eclipse输出的class文件的所在的包的位置,我的目录是在“F:\java_code\TestJNI\bin”,然后执行如下命令:
F:\java_code\TestJNI\bin>javah com.jni.test.TestMain
这样会在bin目录下生成一个“com_jni_test_TestMain.h”文件。
新建VC++动态链接库工程,在工程的包含目录中新增jvm的头文件。如下图所示:
#include "com_jni_test_TestMain.h"
static int g_num = 0;
jboolean JNICALL Java_com_jni_test_TestMain_printInfo(JNIEnv *, jobject)
{
printf("hello world!\n");
return true;
}
jint JNICALL Java_com_jni_test_TestMain_getNum(JNIEnv *, jobject)
{
return g_num;
}
void JNICALL Java_com_jni_test_TestMain_setNum(JNIEnv *, jobject, jint num)
{
g_num = num;
}
使用VC环境编译出dll库文件。
使用如下三种方法加载动态库文件:
使用如下三种方法加载动态库文件:
1)使用绝对路径
将TestJNI.dll库文件放置在F:\java_code\TestJNI\bin\目录下。获取该文件的绝对路径来加载:
private static void loadlibrary()
{
System.load(System.getProperty("user.dir") + File.separator + "bin" + File.separator + "TestJNI.dll");
}
2)将动态库文件放在F:\java_code\TestJNI\bin\com包内的任意地方,将该库文件当作资源文件,首先将其复制到当前的class文件相同的目录下,然后调用加载:
System.loadLibrary("TestMain");
该方法需要复制一份库文件:
private static void loadlibrary2() throws UnsatisfiedLinkError
{
try
{
String libpath = System.getProperty("java.library.path");
if (libpath == null || libpath.length() == 0)
{
throw new RuntimeException("java.library.path is null");
}
String path = null;
StringTokenizer st = new StringTokenizer(libpath, System.getProperty("path.separator"));
if (st.hasMoreElements())
{
path = st.nextToken();
}
else
{
throw new RuntimeException("can not split library path:" + libpath);
}
Class<TestMain> thisClass = TestMain.class;
InputStream inputStream = thisClass.getResource("TestJNI.dll").openStream();
File dllFile = new File(new File(path), "TestMain.dll");
if (!dllFile.exists())
{
FileOutputStream outputStream = new FileOutputStream(dllFile);
byte[] array = new byte[8192];
for (int i = inputStream.read(array); i != -1; i = inputStream.read(array))
{
outputStream.write(array, 0, i);
}
outputStream.close();
}
System.loadLibrary("TestMain");
}
catch (UnsatisfiedLinkError e)
{
throw e;
}
catch (Throwable e)
{
throw new RuntimeException("load RegistryUtil.dll error!", e);
}
}
3)该方法理论上可行,但由于JVM的环境变量更改不能生效,导致无法加载。
private static void loadlibrary3()
{
URL classURL = TestMain.class.getResource("TestMain.class");
String classPath = new File(classURL.getPath()).getParent();
String syslibpath = System.getProperty("java.library.path") + classPath + ";";
System.setProperty("java.library.path", syslibpath);
for(String path: System.getProperty("java.library.path").split(";"))
{
System.out.println(path);
}
System.loadLibrary("TestJNI");
}
最后,编写Java调用native方法的主函数:
public static void main(String[] args)
{
TestMain handle = new TestMain();
handle.printInfo();
handle.setNum(10000);
System.out.println(handle.getNum());
}
输出结果为:
10000
hello world!
奇怪的是,handle.printInfo();调用的结果还晚于System.out.println(handle.getNum());输出在屏幕上面。查了一些资料,愿因在于windows使用的是全缓冲而非行缓冲,只有触发刷新缓冲时才会将内容输出。因此\n是不起作用的。