java 调用 dll

来源:互联网 发布:盆景种子淘宝哪家好 编辑:程序博客网 时间:2024/05/18 10:34

1.

 

Java调用C/C++编写的第三方dll动态链接库(非native API)--- JNI

注:2013年6月6日,我对该博文进行了修改,增加了源代码以及更直观详细的讲解。如果需要代码,可以到文章最后给出的链接进行下载。

 

最近在用weka做一个数据挖掘相关的项目,不得不说,weka还是一个不错的开放源代码库,提供了很多最常用的分类和聚类算法。

在我的项目中要用到一个聚类算法,Affinity Propagation(AP),由多伦多大学的Brendan J. Frey发表于2007年。相比其他的聚类算法,AP算法的聚类结果更加准确。

在AP的官方网站公布了AP算法的动态链接库,我的目标就是实现在Java工程中调用这个动态链接库。

在网上查了资料,发现,如果仅仅是想调用Windows的Native API还是比较省事的,这里我主要针对第三方dll的调用。

下面进入正题。

 

这里主要用的方法是JNI。在网上查资料时看到很多人说用JNI非常的复杂,不仅要看很多的文档,而且要非常熟悉C/C++编程。恐怕有很多人在看到诸如此类的评论时已经决定绕道用其他方法了。但是,假如你要实现的功能并不复杂(简单的参数传递,获取返回值等等),我还是支持使用这个方法的。

Java Native Interface,简称JNI,是Java平台的一部分,可用于让Java和其他语言编写的代码进行交互。下面是从网上摘取的JNI工作示意图。

                     图1   JNI的工作模式

下面就举具体的例子说明一下使用步骤

先说明一下我们要达到的目的

假设我们现在有一个第三方dll叫做ThirdPartyDll.dll,我们想调用其中的realfunction方法,该方法的接口在ThirdParty.h中给出,如下:

 1 // The ThirdPartyDll.dll contains the following function. 2 // Name:  3 //   realfunction 4 // Function: 5 //   Turn a double type array to an int type array. 6 // Parameters: 7 //   nDoubleArray: a double type array needed to cutoff. 8 //   nSize: the size of nDoubleArray. 9 //   nPrint: print the original array if nPrint == true.10 // Return Value:11 //   This function will allocate a new int type array inside for storing the result.12 //   So, DO NOT forget to release the space after using the result! 13 //   (Of course, we'd better not to design a function like this when coding:))14 // ----------------------------------------------------------------------------------15 16 int* realfunction(double* nDoubleArray, unsigned int nSize, bool nPrint);

我们现在要尝试根据这个dll和这个函数接口,在java程序中通过一个中介dll调用该函数。

1) 编写一个类,声明native方法

 1 public class CallThirdParty { 2        3       public native int[] CallThirdPartyDll(double[]    arg_DoubleArray, 4                                             int         arg_SizeofArray, 5                                             boolean     arg_print); 6       static 7       { 8           System.loadLibrary("MediumDll"); 9       }10 }

上面是CallThirdParty.java文件,定义了一个CallThirdParty类,其中有一个方法CallThirdPartyDll(),需要传递三种不同类型的参数,并且返回一个整型数组。

注意,这里只需要声明这个方法,并不需要实现,具体实现就在MediumDll中。

MediumDll就像中介一样,Java通过调用这个中介Dll中的CallThirdPartyDll方法,间接调用真正的第三方Dll。

2)编译生成.h文件

cmd到CallThirdParty.java目录下,执行以下几个命令,生成.h文件。(需要设定java环境变量)

第一步:

javac  CallThirdParty.java 生成CallThirdParty.class

第二步:

javah  CallThirdParty 生成CallThirdParty.h头文件,内容如下:

 1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class CallThirdParty */ 4  5 #ifndef _Included_CallThirdParty 6 #define _Included_CallThirdParty 7 #ifdef __cplusplus 8 extern "C" { 9 #endif10 /*11  * Class:     CallThirdParty12  * Method:    CallThirdPartyDll13  * Signature: ([DIZ)[I14  */15 JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll16   (JNIEnv *, jobject, jdoubleArray, jint, jboolean);17 18 #ifdef __cplusplus19 }20 #endif21 #endif

注意,CallThirdParty.h这个头文件的内容是不能修改的,否则JNI会找不到相对应的CallThirdPartyDll()的实现。

3)创建C/C++工程,实现CallThirdPartyDll()方法。

创建一个C/C++工程,工程名为MediumDll(其实,生成的dll名为MediumDll即可),导入上一步生成的CallThirdParty.h这个头文件以及官方给出的接口文件ThirdParty.h,并创建一个CPP文件,实现CallThirdParty.h文件中的方法。

        图2   新建工程结构

由于我默认创建的工程是win32控制台程序并且最后生成的是.exe文件,所以还要做一步工程属性修改,让它生成.dll后缀文件。

打开Project Property ->General,做以下修改:

                                  图3   修改工程属性

下面就是实现 JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll (JNIEnv *, jobject, jdoubleArray, jint, jboolean);这个方法了。先贴代码再慢慢解释吧。

 1 #include "CallThirdParty.h" 2 #include "ThirdParty.h" 3 #include <iostream> 4 #include <Windows.h> 5 using namespace std; 6  7 #ifdef __cplusplus 8 extern "C" { 9 #endif10 11 typedef int* (*ThirdPartyFunc)(double*, unsigned int, bool);12 13 JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll (JNIEnv *env, jobject _obj, jdoubleArray _arg_doublearray, jint _arg_int, jboolean _arg_boolean)14 {15     HMODULE dlh = NULL;16     ThirdPartyFunc thirdPartyFunc;17 18     if (!(dlh=LoadLibrary("ThirdPartyDll.dll")))      19     {20     printf("LoadLibrary() failed: %d\n", GetLastError()); 21     }22     if (!(thirdPartyFunc = (ThirdPartyFunc)GetProcAddress(dlh, "realfunction")))  23     {24         printf("GetProcAddress() failed: %d\n", GetLastError()); 25     }26 27     int        m_int = _arg_int;  28     double*    m_doublearray = env->GetDoubleArrayElements(_arg_doublearray, NULL);29     bool       m_boolean = _arg_boolean;30 31     int* ret = (*thirdPartyFunc)(m_doublearray, m_int, m_boolean); /* actual function call */32     33     jintArray result = NULL;34     if (ret)35     {36         result = env->NewIntArray(_arg_int);37         env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);38     }39 40     FreeLibrary(dlh); /* unload DLL and free memory */41     if(ret) 42     {43         free(ret); 44     }45 46     return result;47 }48 49 #ifdef __cplusplus50 }51 #endif

a)首先为了#include <jni.h>,必须添加JNI所在的目录。

打开Project Property -> C/C++ -> General -> Additional Include Directories添加相应目录:

                                                         图4   添加JNI目录

b)在CallThirdParty.h文件中自动生成的函数,只标识了函数参数类型,为了引用这些参数,自己起一个相应的名字:

JNIEXPORT jintArray JNICALL Java_CallThirdParty_CallThirdPartyDll
(JNIEnv *env, jobject _obj, jint _arg_int, jdoubleArray_arg_doublearray, jboolean _arg_boolean) ......

c)声明函数指针,就是你要调用的第三方dll中函数的类型。

d)LoadLibrary,导入真正的第三方Dll,并找到要调用的方法的函数地址

把这个函数地址赋值给函数指针,接下来就可以通过这个函数指针调用真正的realfunction函数了!

e)类型转换:

读读jni.h文件就知道jdouble和double其实是一个东西,jboolean就是unsigned char类型,jni.h中是这么声明的:

1 typedef unsigned char   jboolean;2 typedef unsigned short  jchar;3 typedef short           jshort;4 typedef float           jfloat;5 typedef double          jdouble;

但是数组类型就没有这么简单,获取数组要使用类型相对应的env->GetTypeArrayElement(jTypeArray...)。

最后,要返回一个jint类型的数组,就要新创建一个此类型的数组,再为其赋值:

1 jintArray result = env->NewIntArray(_arg_int);2 env->SetIntArrayRegion(result, 0, _arg_int, (const jint*)ret);

其中,_arg_int代表的是创建数组的长度。

最后return result。

4)Build这个工程。

Build,生成相应的MediumDll.dll文件,并将其与真正要调用的第三方动态链接库ThirdPartyDll.dll放到java工程目录下。

               图5   将生成的dll放到java工程下

5)编写测试java程序,调用dll库。

以下为测试程序,Test.java:

 1 public class Test { 2      public static void main(String[] args) { 3           4          double doubleArray[] = {1.1, 2.5, 5,2}; 5           6          CallThirdParty callThirdParty = new CallThirdParty(); 7          int cutOffArray[] = callThirdParty.CallThirdPartyDll(doubleArray, 3, true); 8           9          for (int i = 0; i < cutOffArray.length; ++i)10              System.out.println(cutOffArray[i]);11      }12 }

运行,控制台输出结果:

Value of 0: 1.1
Value of 1: 2.5
Value of 2: 5
1
2
5

到此,java调用第三方dll就基本完成了。

本文也主要是介绍大概的操作流程,至于具体应该使用哪些API就只有去研究官方文档了。

另外还有一些需要注意的问题,比如64位的程序去调用32位的dll会报错啊等等...这些都是细节问题了。

最后,个人认为,自己动手实践还是很重要,网上都说这个复杂那个难,但是至于难还是不难,还是要实践了才知道...不能不去尝试...

-----------------------------------------------

最后的最后,附上相关代码的链接:https://github.com/AnnieKim/ForMyBlog/tree/master/20120101

其中,projects文件夹下是工程文件,在eclipse和vs2008下进行,如果可以直接运行那就最好了。

另外,source文件夹下是所有的相关代码,包括第三方dll和生成的Mediumdll。

如果遇到什么问题,欢迎讨论。

(完)

 

 

 

===========================================================================================================================

 

 

 

2.

http://tvjody.iteye.com/blog/125643

Java调用.dll文件

    博客分类:
  • Java EE
Java应用服务器WindowsLinuxJVM 

因为项目的需求,要在JAVA项目中调用Windows的Dll(动态链接库)文件,之前用Jni调用过C写的Dll文件,比较麻烦,这里不多说,网上也有很多这方面的文档。在网上找到一个开源的组件JNative,使用后感觉比较方便。

  • 下截JNative组件

    jnative.sourceforge.net/到这里下载JNative开源项目,我下载的是1.3.2

  • 解压JNative-<st1:chsdate isrocdate="False" islunardate="False" day="30" month="12" year="1899">1.3.2</st1:chsdate>.zip

    获得三个文件,分别是:JNativeCpp.dll,libJNativeCpp.so,JNative.jar 。
    JNativeCpp.dll Windows下用的,拷贝到windows / system32目录下;
    libJNativeCpp.so Linux下的,拷贝到系统目录下;
    JNative.jar 这是一个扩展包,导入工程LIB中或将其拷贝到jdk\jre\lib\ext 下,系统会自动加载。

  • 使用说明

    我的项目将使用JNative组件调用一个测试应用服务器状态的TestAppSvr.dll文件,Dll文件中包含一个TestConnect()方法,返回一个整形的结果(1或0)

    首先配置好JNative组件的windows环境:
    将Native要用到JNativeCpp.dll放在系统盘的\WINDOWS\system32

    将JNative.jar导入工程中,新建一个调用类:

    java 代码
    1. package com.tvjody;   
    2.   
    3. import java.io.File;   
    4. import java.io.FileOutputStream;   
    5. import java.io.IOException;   
    6. import java.io.InputStream;   
    7.   
    8. import org.xvolks.jnative.JNative;   
    9. import org.xvolks.jnative.Type;   
    10. import org.xvolks.jnative.exceptions.NativeException;   
    11.   
    12. public class AppSvrTestConnect {   
    13.   
    14.     public AppSvrTestConnect() {   
    15.   
    16.     }   
    17.        
    18.     /**  
    19.      * 测试应用服务器连接状态  
    20.      *   
    21.      *  TestConnect   
    22.      * @param ip 应用服务器IP  
    23.      * @param port 端口  
    24.      * @param intrcpt  是否采用数据压缩方式 1 :true 0:false  
    25.      * @return int 1 :成功 0:失败  
    26.      * @throws NativeException  
    27.      * @throws IllegalAccessException  
    28.      */  
    29.     private static final int TestConnect(String ip, int port, int intrcpt)throws NativeException, IllegalAccessException {   
    30.         JNative n = null;   
    31.         try {              
    32.             n = new JNative("TestAppSvr.dll", "TestConnect");   
    33.             n.setRetVal(Type.INT);   
    34.             int i = 0;   
    35.             n.setParameter(i++, Type.STRING, ip);   
    36.             n.setParameter(i++, Type.INT, "" + port);   
    37.             n.setParameter(i++, Type.INT, "" + intrcpt);   
    38.             n.invoke();   
    39.             return Integer.parseInt(n.getRetVal());   
    40.         } finally {   
    41.             if (n != null)   
    42.                 n.dispose();   
    43.         }   
    44.     }   
    45.     /**  
    46.      * 指定Dll文件路径,动态加载本地链接库,测试应用服务器连接状态  
    47.      * setDllPath  
    48.      * @param path Dll文件的路径,不包含DLL名称 例如:windows - d:\test\test\ unix - root/test/test/  
    49.      * @param ip 应用服务器IP  
    50.      * @param port 端口  
    51.      * @param intrcpt  是否采用数据压缩方式 1 :true 0:false  
    52.      * @return int 1 :成功 0:失败  
    53.      * @throws NativeException  
    54.      * @throws IllegalAccessException  
    55.      */  
    56.     public static final int TestConnectFromDllPath(String path,String ip, int port, int intrcpt) throws NativeException, IllegalAccessException{   
    57.         path += "TestAppSvr.dll";   
    58.         System.load(path);   
    59.         return TestConnect(ip,port,intrcpt);   
    60.     }   
    61.     /**  
    62.      * Dll文件放在JRE\bin目录下面,ClassLoader就能通过System.loadLibrary()动态加载本地链接库  
    63.      * TestConnectFromDllPath  
    64.      * @param ip 应用服务器IP  
    65.      * @param port 端口  
    66.      * @param intrcpt  是否采用数据压缩方式 1 :true 0:false  
    67.      * @return int 1 :成功 0:失败  
    68.      * @throws NativeException  
    69.      * @throws IllegalAccessException  
    70.      */  
    71.     public static final int TestConnectFromDllPath(String ip, int port, int intrcpt) throws NativeException, IllegalAccessException{   
    72.         System.loadLibrary("TestAppSvr");   
    73.         return TestConnect(ip,port,intrcpt);   
    74.     }   
    75. }  

    这个类实现了一个静态私有方法,用来调用Dll文件中的方法返回结果

    private static final int TestConnect(String ip, int port, int intrcpt)

    两个静态公共方法,分两种方式装载DLL文件

    public static final int TestConnectFromDllPath(String path,String ip, int port, int intrcpt)  //通过DLL文件的路径
    public static final int TestConnectFromDllPath(String ip, int port, int intrcpt) //通过ClassLoader

             然后新建一个类,调用AppSvrTestConnect.java,实现方法一调用,我是将TestAppSvr.dll文件与Demo.java放在一个目录下 ,所以得到Demo.java的路径后就可以得到TestAppSvr.dll的路径,调用AppSvrTestConnect.TestConnectFromDllPath()方法后就能返回正确的信息.方法二是已经将TestAppSvr.dll放在了Jre\bin目录下,在JVM的Classloader的时候会自动加载,然后通过System.loadLibrary("TestAppSvr")就可以装配DLL文件.

java 代码
  1. public class Demo {   
  2.     public int getInfo() throws NativeException, IllegalAccessException{   
  3.            
  4.         String path=getClass().getResource(File.separator).getPath();          
  5.         path = path.substring(1,path.length());   
  6.         System.out.println(path);   //得到DLL文件的路径   
  7.            
  8.         String ip = "192.168.0.48"; //服务器IP   
  9.         int port = 221;             //端口   
  10.         int intrcpt = 1;            //数据压缩方式传送,1为采用;0为不采用   
  11.         //方法1 传入Dll文件的路径   
  12.         //int info = AppSvrTestConnect.TestConnectFromDllPath(path, ip, port, intrcpt);   
  13.            
  14.         //方法2 Dll文件已经放在JRE\bin目录下面   
  15.         int info = AppSvrTestConnect.TestConnectFromDllPath(ip, port, intrcpt);   
  16.            
  17.         //1为成功,0为失败   
  18.         if (info == 1)   
  19.             System.out.println("应用服务器可用。");   
  20.         else  
  21.             System.out.println("应用服务器不可用,请检查IP地址和端口是否正确。");   
  22.            
  23.         return info;   
  24.     }   
  25.       


 System.loadLibrary():装载Windows\System32下或jre\bin或Tomcat\bin目录下的本地链接库

System.load():根据具体的目录来加截本地链接库,必须是绝对路径

 

  • 备注

    上面的示例工程,因为是例子,所以没有大多的设计,只是实现了装载DLL文件,调用DLL文件方法,返回信息.

    JNative的详细说明,请参考JNative的源程序和例子.

    注意JVM只允许一个默认的ClassLoader来load native library,同时并不提供专门的API来unload一个loaded native library,所以在项目调试的时候,独立启动Web Server.