2019独角兽企业重金招聘Python工程师标准>>>
先说明一下什么时候会使用到这个技术,最近公司要求在第三方地图引擎上做热力图,碰巧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#的类库:
文件信息如下:
代码如下:
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类文件。
代码如下:
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)。
调整完后,打开红色部分的路径,可以看到工具已经自动帮我们编译好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图:
h文件会在classes目录下生成,然后把它拷贝到工程的src目录下。
文件内容如图,可以看到定义的包名和方法,声名方式:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* 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项目:
接下来要设置一下项目属性,解决方案资源管理器 --> TestBuildC --> 属性;
配置:活动Debug --> Release;
配置管理器:活动解决方案平台 --> 新建x64(32位或64位文件在此设置);
配置属性:
常规 --> 公共语言运行时支持:公共语言运行时支持(/clr);
C/C++ --> 代码生成 --> 运行库:多线程DLL(/MD),启用增强指令集:是(/Gy);
添加支持的h头文件和dll文件到项目中,找到以下文件拷贝到C++工程目录里:
JDK所在目录/include/jni.h
JDK所在目录/include/win32/jni_md.h
生成的java项目类的h头文件 test_JniDllTest.h
生成的C# dll文件 TestBuildC.dll
接着再工程里,添加引用资源文件,右键资源文件添加现有项,将上面3个h头文件添加进来:
打开test_JniDllTest.h文件修改头部引用,将第二行的引用方式修改一下:
#include
改为
#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
#include
#include
// 引入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, "", "([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 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));
}
最后,点击菜单栏 --> 生成 --> 生成TestBuildC,在项目/x64/Debug文件夹中找到生成的64位Dll文件,第一次点击生成可能会出现报警,忽略掉再次生成一次,即可。
五、测试
终于到测试这一步了,将生成的C++和C# dll文件放入JDK/bin目录下,将生成的test_JniDllTest.h文件放入java工程的src根目录下,运行类文件进行测试。
常见问题:
- 确保JDK的环境变量配置正确,才可以正常运行javah -jni 生成头文件的命令。
- 在MyEclipse IDE工具中进行测试,确保所引用的JDK正确,才能正常调用bin目录下存放的dll文件。
六、参考案例
- http://7wolfs.iteye.com/blog/2043835
- http://blog.chinaunix.net/uid-22197900-id-3044019.html