先说明一下什么时候会使用到这个技术,最近公司要求在第三方地图引擎上做热力图,碰巧API中没有相关的接口,只能通过添加图片的方式进行显示,所以只能在后台代码中生成热力图,在往上面叠加,Java生成热力图并没有现成的代码可用(QAQ技术不够,求技术帝给份生成HeatMap的代码),C#中有,于是经理给搞出了热力图生成,所以需要到Java去调用C# dll来完成该功能,在其他方面,比如,使用dll来封装坐标计算公式,因为有些东西是要保密的所以要这样做处理,对dll文件使用网卡签名加密(安全,绑定网卡)等,这些都是非常有必要的,对于无法实现的功能采用跨平台的方式去实现它,也不失为完成任务目标的一种方法。



引用说明

C++和C#是不一样的。Java无法直接调用C# dll,需要经过桥接的方式,进行中继转发一下请求,通过管理性的C++桥接方式,成功完成了Java调用C# dll(这段话是在网上看到的,引用进行说明,具体引用流程是:Java --> C++ --> C#)。



DLL介绍

DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。



软件工具

Microsoft Visual Studio 2010、MyEclipse 2014



一、C# DLL文件生成

建立一个C#的类库:

javacpp调用dll java调用dll实例_c#

文件信息如下:

javacpp调用dll java调用dll实例_java_02

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestBuildCS
{
    public class General
    {
        private int result;
        public int Result
        {
            get { return result + 10; }
            set { this.result = value; }
        }
        public string outPutMess(string str1)
        {
            Console.WriteLine("20170420成功调用了dll");
            return "CS Information:" + str1;
        }
        public string submit(string str1, string str2)
        {
            Console.WriteLine("成功调用了dll");
            return "CS:" + str1 + ":" + str2;
        }
        public bool testBoolean(string str1, string str2)
        {
            if (str1.Equals("true"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

重要参数说明:

DLL文件名:TestBuildCS

工作空间(namespace):TestBuildCS

类名(class):General

方法名(method):outPutMess

传入值类型:string

返回值类型:string

保存后,点击顶排按钮栏,生成,即可在以下路径找到生成好的dll文件。

例:E:\VSWorkspace\Projects\TestBuildCS\TestBuildCS\bin\Debug\TestBuildCS.dll



二、Java 类文件生成

先下载JNative的Jar包文件,加入到Java工程中,并建立JniDllTest类文件。

javacpp调用dll java调用dll实例_System_03

代码如下:

package test;

public class JniDllTest {

	/**
	 * native声名
	 * C#对应说明:
	 * 1.方法名 outPutMess
	 * 2.传入参数类型 String
	 * 3.返回参数类型 String
	 * @param mess
	 * @return String
	 */
	public native String outPutMess(String mess);
	public native String submit(String a, String b);     
    public native int add(int a, int b);     
    public native boolean testBoolean(String a, String b);
	
	/**
	 * 此处引用C++ Dll
	 */
	static {
		//System.loadLibrary方法使用前提,必须将引用的JDK路径加入到环境变量Path中
		//System.loadLibrary方法默认引用当前工作空间引用JDK的bin目录下的dll文件,不需要传入后缀名
        System.loadLibrary("TestBuildC");
        //System.load方法参数必须为库文件的绝对路径,可以是任意路径
        //System.load("D:\\DevelopeSoft\\Java\\jdk1.7.0_79\\bin\\TestBuildC.dll");
    }
	
    public static void main(String[] args) {     
    	JniDllTest t = new JniDllTest();     
        System.out.println(t.outPutMess("OK!"));  
//      System.err.println(t.submit("user", "pass"));  
//      System.out.println(t.add(2, 20));     
//      System.err.println(t.testBoolean("1", "pass"));
    }  
	
}

重要参数说明:

类文件名:JniDllTest

包路径:test

注意:方法名,传入参数类型,返回值类型,必须与C#中的一致。

System.loadLibrary()装载的是c++的dll文件,不是C#的,做到这一步可以先假定一个文件名。



三、H头文件生成

生成JNI所需的h头文件,并存放在Java工程的src根目录下,这里使用了MyEclipse IDE工具,所以可以直接找到classes文件生成的目录,如果没有使用相关工具,请度娘如何生成java编译后的classes文件,因为这里会直接指向编译后的classes文件,而不是java文件。

使用了IDE的看这里,找到编译目录,这里以普通Java工程为例(手动调整了一下编译目录,原来默认是bin目录下,我调整为WEB的默认方式WebRoot\WEB-INF\classes)。

javacpp调用dll java调用dll实例_开发工具_04

调整完后,打开红色部分的路径,可以看到工具已经自动帮我们编译好classes文件了。

使用命令:进入到classes编译目录下,并执行javah编译。

编译命令:javah -jni 包名.类名

注意:这个路径下对应的JniDllTest是classes文件,不是java文件的所在路径,编译后的h文件是要放在工程src根目录下的,所以要确定包名是否正确,编译的当前目录为clsses,具体视情况而定根目录。

E:
CD E:\Workspaces\MyEclipse Professional 2014\dllTest\WebRoot\WEB-INF\classes
JAVAH -JNI test.JniDllTest

CMD图:

javacpp调用dll java调用dll实例_System_05

h文件会在classes目录下生成,然后把它拷贝到工程的src目录下。

javacpp调用dll java调用dll实例_System_06

文件内容如图,可以看到定义的包名和方法,声名方式:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_JniDllTest */

#ifndef _Included_test_JniDllTest
#define _Included_test_JniDllTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test_JniDllTest
 * Method:    outPutMess
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_test_JniDllTest_outPutMess
  (JNIEnv *, jobject, jstring);

/*
 * Class:     test_JniDllTest
 * Method:    submit
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_test_JniDllTest_submit
  (JNIEnv *, jobject, jstring, jstring);

/*
 * Class:     test_JniDllTest
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_test_JniDllTest_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     test_JniDllTest
 * Method:    testBoolean
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
 */
JNIEXPORT jboolean JNICALL Java_test_JniDllTest_testBoolean
  (JNIEnv *, jobject, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif



四、C++ DLL文件生成

建立一个C++ Win32项目:

javacpp调用dll java调用dll实例_javacpp调用dll_07

javacpp调用dll java调用dll实例_javacpp调用dll_08

接下来要设置一下项目属性,解决方案资源管理器 --> TestBuildC --> 属性;

配置:活动Debug --> Release;

配置管理器:活动解决方案平台 --> 新建x64(32位或64位文件在此设置);

配置属性:

常规 --> 公共语言运行时支持:公共语言运行时支持(/clr);

C/C++ --> 代码生成 --> 运行库:多线程DLL(/MD),启用增强指令集:是(/Gy);

javacpp调用dll java调用dll实例_开发工具_09

javacpp调用dll java调用dll实例_System_10

添加支持的h头文件和dll文件到项目中,找到以下文件拷贝到C++工程目录里:

JDK所在目录/include/jni.h

JDK所在目录/include/win32/jni_md.h

生成的java项目类的h头文件 test_JniDllTest.h

生成的C# dll文件 TestBuildC.dll

javacpp调用dll java调用dll实例_System_11

接着再工程里,添加引用资源文件,右键资源文件添加现有项,将上面3个h头文件添加进来:

javacpp调用dll java调用dll实例_c#_12

打开test_JniDllTest.h文件修改头部引用,将第二行的引用方式修改一下:

#include <jni.h>
改为
#include "jni.h"

编辑TestBuildC.cpp文件代码如下:

// CPP.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"      
#include "jni.h"   
#include "jni_md.h"
// 引用生成的h头文件
#include "test_JniDllTest.h"
#include "string.h"

#include <malloc.h>
#include <stdlib.h>
#include <vcclr.h> 

// 引入c#的库
#using "TestBuildCS.dll"
// 引入c#的命名空间
using namespace TestBuildCS;   

// 其他引用
#using "System.dll"
#using "System.Web.dll"
#using "System.Web.Services.dll"

using namespace System;
using namespace System::Text;
using namespace System::Web;
using namespace System::Web::Services;
using namespace System::ComponentModel;

// 转换方法 start
// char* To jstring
jstring stringTojstring(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); 
}
// jstring To char*
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 To String
String^ jstringToStr(JNIEnv* env, jstring jstr)
{
	char* str = jstringTostring(env, jstr);
	String^ value = gcnew String(str);
	free(str);
	return value;
}

// String To jstring
jstring strTojstring(JNIEnv* env, String^ rtn)
{
	pin_ptr<const wchar_t> wch = PtrToStringChars(rtn);
	size_t convertedChars = 0;
	size_t sizeInBytes = ((rtn->Length + 1) * 2);
	char *ch = (char *)malloc(sizeInBytes);
	errno_t err = wcstombs_s(&convertedChars, 
		ch, sizeInBytes,
		wch, sizeInBytes);
	jstring js = stringTojstring(env, ch);
	free(ch);
	return js;
}
// 转换方法 end

// 注意看这里是如何声名方法的,再进行修改(test包,JniDllTest类,outPutMess方法)
JNIEXPORT jstring JNICALL Java_test_JniDllTest_outPutMess
  (JNIEnv *env, jobject obj, jstring str1)  
{   
    //c#中的对象 General  
    General ^o = gcnew General();   
    return strTojstring(env, o->outPutMess(jstringToStr(env,str1)));   
}

JNIEXPORT jstring JNICALL Java_test_JniDllTest_submit
  (JNIEnv *env, jobject obj, jstring str1, jstring str2)  
{   
    //c#中的对象   
    General ^o = gcnew General();   
    return strTojstring(env, o->submit(jstringToStr(env,str1), jstringToStr(env,str2)));   
}

JNIEXPORT jint JNICALL Java_test_JniDllTest_add   
  (JNIEnv *env, jobject obj, jint a, jint b)   
{   
    //c#中的对象   
    General ^o = gcnew General();   
    o->Result = a + b;   
    return o->Result;   
}

JNIEXPORT jboolean JNICALL Java_test_JniDllTest_testBoolean
  (JNIEnv *env, jobject obj, jstring str1, jstring str2)  
{   
    //c#中的对象   
    General ^o = gcnew General();   
    return o->testBoolean(jstringToStr(env,str1), jstringToStr(env,str2));  
}

javacpp调用dll java调用dll实例_开发工具_13

最后,点击菜单栏 --> 生成 --> 生成TestBuildC,在项目/x64/Debug文件夹中找到生成的64位Dll文件,第一次点击生成可能会出现报警,忽略掉再次生成一次,即可。



五、测试

终于到测试这一步了,将生成的C++和C# dll文件放入JDK/bin目录下,将生成的test_JniDllTest.h文件放入java工程的src根目录下,运行类文件进行测试。

javacpp调用dll java调用dll实例_javacpp调用dll_14

常见问题:

  1. 确保JDK的环境变量配置正确,才可以正常运行javah -jni 生成头文件的命令。
  2. 在MyEclipse IDE工具中进行测试,确保所引用的JDK正确,才能正常调用bin目录下存放的dll文件。


六、参考案例