最近项目需要用到java调用dll库。于是学习了一番,中间遇到一些问题,这里记录一下整体过程。
首先是把需要调用的函数用java写出来。这里写两个函数:
package com.tgb.controller;
/**
* Created by Chan on 2016/6/24.
*/
public class picture {
static
{
System.loadLibrary("puppet");
}
public native static String compare(String disign , String scan , int param);
public native static String test(int i);
}
特别需要注意的地方是这里有package com.tgb.controller。后来才发现有package和没有package的区别是相当大的。
然后需要编译这个文件。因为是在SpringMVC里面做的,这里的路径是
springmvcTest\src\main\java\com\tgb\controller\picture.java
然后在controller目录下,shift+鼠标右键调出cmd窗口,然后javac编译这个文件。
javac picture.java
接着,需要生成一个c++用的头文件。转到目录java下,即
springmvcTest\src\main\java\
用javah命令生成头文件。
javah com.tgb.controller.picture
没有后缀名,命令执行完之后就会在java目录下生成一个叫com_tgb_controller_picture.h的文件,这就是我们后来要用到的头文件。
如果picture.java没有包的话直接在与它相同的目录下javah picture就可以了。
然后打开VS,开始c++这边的编写。不过在那之前需要做些准备工作。
首先,将以下三个文件拷贝到工程目录下,或者是放到VS/VC/include/下
- com_tgb_controller_picture.h
- JDK/include/jni.h
- JDK/include/win32/jni_md.h
然后打开VS。。。。
打开之后新建项目–win32项目—然后见下图:
然后点完成就行了。
接着新建一个源文件,头三句话就是这个:
#include"jni.h"
#include"jni_md.h"
#include"com_tgb_controller_picture.h"
这里先不接着写,我们先打开com_tgb_controller_picture.h看一下有些啥。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_tgb_controller_picture */
#ifndef _Included_com_tgb_controller_picture
#define _Included_com_tgb_controller_picture
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_tgb_controller_picture
* Method: compare
* Signature: (Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_tgb_controller_picture_compare
(JNIEnv *, jclass, jstring, jstring, jint);
/*
* Class: com_tgb_controller_picture
* Method: test
* Signature: (I)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_tgb_controller_picture_test
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
一大堆东西其实不用管,可以看到里面声明了两个函数:
JNIEXPORT jstring JNICALL Java_com_tgb_controller_picture_compare (JNIEnv *, jclass, jstring, jstring, jint);
JNIEXPORT jstring JNICALL Java_com_tgb_controller_picture_test (JNIEnv *, jclass, jint);
可以看到函数名还是比较直观,前两个参数不用管,我们需要的参数从第三个开始。这就是我们之前在java写的两个方法的对应,我们只需要在这里实现这两个函数就行了。
源文件如下:
#include"jni.h"
#include"jni_md.h"
#include"com_tgb_controller_picture.h"
#include<stdlib.h>
#include<string.h>
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
JNIEXPORT jstring JNICALL Java_com_tgb_controller_picture_compare(JNIEnv * env, jclass, jstring design,
jstring scan, jint param) {
char *desChar = jstringTostring(env, design);
char *scanChar = jstringTostring(env, scan);
FILE *designpic = fopen(desChar,"r");
FILE *scanpic = fopen(scanChar, "r");
if (designpic == NULL || scanpic == NULL) {
return stoJstring(env, "cannot open file.");
}
fclose(designpic);
fclose(scanpic);
return stoJstring(env, "files open succeed");
}
JNIEXPORT jstring JNICALL Java_com_tgb_controller_picture_test
(JNIEnv * env, jclass, jint n) {
if (n == 0) {
return stoJstring(env, "test succeed");
}
else
return stoJstring(env, "test failure");
}
char* jstringTostring(JNIEnv* env, jstring jstr)和jstring stoJstring(JNIEnv* env, const char* pat)是两个类型转换用的函数,因为java中的string和c++的字符串还有点不一样,所以在用的时候转换一下会方便很多。函数的功能很简单,看代码就行了。
接着,我们需要生成dll库,首先看java是多少位的。
如果是64位的就
32位的就
然后生成解决方案就可以了。
然后dll文件可以在VS项目中的debug文件夹中找到。将这个文件放到java项目目录下,或者C:\windows\system32目录下都可以。
然后在回到java中,我们写了一个简单的HelloController
package com.tgb.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("/")
public class HelloController {
@RequestMapping(method = RequestMethod.GET)
public String printWelcome(ModelMap model) {
model.addAttribute("message", "Hello world!");
int n = 0;
System.out.println("been here");
String ret = picture.compare("D:\\1.jpg","D:\\2.jpg",1);
String testRet = picture.test(n);
System.out.println(ret+" "+testRet);
return "hello";
}
}
然后直接跑。控制台输出如下:
说明本地库调用成功,至于为什么调用了三次有待进一步的学习。初步猜想是@RequestMapping(“/”)map到了所有的请求。