这两天被JNI折腾疯了,而其中困扰我时间最长的问题竟然都是环境问题。。总结一下JNI调用原始dll(不是包含java生成的头文件之后再生成的dll)的过程。
JNI能调用的是符合规范的dll程序,需要根据javah得到的头文件生成。因此当需要调用一个原始dll时,需要我们自己再写一个符合jni规范的dll,来调用原始dll。也就是一个中间层。
1、java生成头文件
用eclipse写完以下代码:
package com.aaa.netcard;
public class Netcard {
public native void displayHelloWorld();
public native int displayPlus(int a, int b);
public native int displayCalculator(int a, int b);
static {
System.loadLibrary("calculator");
}
public static void main(String[] args){
Netcard card = new Netcard();
System.out.println(card.displayCalculator(5, 99));
}
}
写完以后保存,eclipse就会在bin文件夹下自动生成class文件。在bin目录下打开cmd,输入javah -jni com.aaa.netcard.Netcard。回车,生成成功。注意要写包名。
生成了com_aaa_netcard_Netcard.h。此文件第三步用到。
2、原始dll。
自己写了一个简单的加减乘的origin.dll。
// origin.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
#include "origin.h"
extern "C"_declspec(dllexport) int plus(int a, int b) {
return a+b;
}
extern "C"_declspec(dllexport) int minus(int a, int b) {
return a-b;
}
extern "C"_declspec(dllexport) int times(int a, int b) {
return a*b;
}
注意关键词extern "C" _declspec(dllexport)。_declspec(dllexport)表示要输出的函数/类,extern "C"指明用C的编译方式。(详见上一篇博文) 3、完成jni调用的dll,对原始dll进行包装。
原始dll是不能直接调用的。需要调用的话需要再写一个dll将原始dll载入。
先打开第一步生成的com_aaa_netcard_Netcard.h看一下。
#ifndef _Included_com_aaa_netcard_Netcard
#define _Included_com_aaa_netcard_Netcard
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_aaa_netcard_Netcard
* Method: displayCalculator
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_aaa_netcard_Netcard_displayCalculator
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
新建一个dll项目calculator,将origin.dll放在calculator/debug目录下,放在这里不用在loadLibrary时写origin.dll的绝对路径。接下来我们就在此dll工程的calculator.cpp中实现java生成的头文件中的方法。将com_aaa_netcard_Netcard.h拷贝到工程目录下并include进来。注意函数名参数等等写法要一致。可以看到从java到c++数据类型经过了转换,int变成了jint。
JNIEXPORT jint JNICALL Java_com_aaa_netcard_Netcard_displayCalculator
(JNIEnv *, jobject, jint, jint);
// calculator.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <jni.h>
#include "calculator.h"
#include "com_aaa_netcard_Netcard.h"
using namespace std;
typedef int(*Dllfun)(int, int);
JNIEXPORT jint JNICALL Java_com_aaa_netcard_Netcard_displayCalculator
(JNIEnv *, jobject, jint a, jint b)
{
Dllfun original;
HINSTANCE hdll;
hdll=LoadLibrary(TEXT("origin.dll"));
if(hdll==NULL){
FreeLibrary(hdll);
}
original=(Dllfun)GetProcAddress(hdll,"plus");
if(original==NULL){
FreeLibrary(hdll);
}
try{
return original(a,b);
} catch (const char * str){
cout<<"fail"<<endl;
return 1111;
}
}
生成了calculator.dll。将此dll粘贴到java项目根目录下。编译运行,控制台输出了调用原始dl得到l的计算结果。成功。