Java调用C#的dll是通过C++作为桥梁,JNI—>C++的dll(clr方式运行)—>C#的dll
引用说明
C++和C#是不一样的。Java无法直接调用C# dll,需要经过桥接的方式,进行中继转发一下请求,通过管理性的C++桥接方式,成功完成了Java调用C# dll(这段话是在网上看到的,引用进行说明,具体引用流程是:Java --> C++ --> C#)。
以下为本次测试的配置环境:
系统:win10 64位
Java开发环境(均为64位):JDK1.8、IntelliJ IDEA 2018
C++开发环境:VS2022
C#开发环境:VS2022
一、C# DLL文件生成
以 Windows x64平台 为例,一个简单的人脸检测Demo。
1、使用 nuget 安装依赖
包名称 说明
ViewFaceCore ViewFaceCore .NET 核心库
ViewFaceCore.all_models 人脸检测的模型支持(图省事可以直接安装这个)
ViewFaceCore.runtime.win.x64 Windows-x64 的本机运行时,其它平台自行选择安装,可安装多个
ViewFaceCore.Extension.SkiaSharp SkiaSharp图像处理扩展,ImageSharp、SkiaSharp、System.Drawing三选一
建立一个C#的类库:
文件信息如下:
C# 代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace PmpFaceCore { public class Hello { public string helloworld() { return "C#"; } public string myconversion(int a, int b) { int c = a + b; return c.ToString(); } } }
保存后,点击项目生成,生成C#项目的dll (很关键的一步,留意选择目标平台 和C++ 保持一致),即可在以下路径找到生成好的dll文件。
目标平台不一致,java 调用时,可能 JVM报错“Failed to write core dump“
二、Java 类文件生成
代码如下:
package vip.xiaonuo.common.util;
import java.io.File;
import java.io.IOException;
public class CommonFaceCore {
// 声明一个本地方法 1
public native String helloworld();
// 声明一个本地方法 2
public native String myconversion(int a, int b);
// 加载C++生成的DLL
static {
System.loadLibrary("PMP");
//System.loadLibrary方法使用前提,必须将引用的JDK路径加入到环境变量Path中
//System.loadLibrary方法默认引用当前工作空间引用JDK的bin目录下的dll文件,不需要传入后缀名
//System.load方法参数必须为库文件的绝对路径,可以是任意路径
//System.load("D:\\Java\\jdk1.7.0_79\\bin\\TestBuildC.dll");
}
public static void main(String[] args) {
CommonFaceCore core = new CommonFaceCore();
System.out.println(core.helloworld());
System.out.println(core.myconversion(2, 9));
}
}
注意:方法名,传入参数类型,返回值类型,必须与C#中的一致。
System.loadLibrary()装载的是c++的dll文件,不是C#的dll,做到这一步可以先设定一个文件名。
三、java类映射 C++的H头文件生成
使用命令:进入到java类文件目录下,并执行javah编译。
编译命令:javah -jni 包名.类名
通过cmd 命令行进入到 测试类.java所在目录,利用 javac -encoding UTF-8 -h ./ CommonFaceCore.java 生成.h的头文件,下一步将该头文件包含进C++工程中
vip_xiaonuo_common_util_CommonFaceCore.h
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class vip_xiaonuo_common_util_CommonFaceCore */ #ifndef _Included_vip_xiaonuo_common_util_CommonFaceCore #define _Included_vip_xiaonuo_common_util_CommonFaceCore #ifdef __cplusplus extern "C" { #endif /* * Class: vip_xiaonuo_common_util_CommonFaceCore * Method: helloworld * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_helloworld (JNIEnv *, jobject); /* * Class: vip_xiaonuo_common_util_CommonFaceCore * Method: myconversion * Signature: (II)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_myconversion (JNIEnv *, jobject, jint, jint); #ifdef __cplusplus } #endif #endif
文件内容如图,可以看到定义的包名和方法,声名方式:
四、C++ DLL文件生成
利用VS2022创建 动态链接库工程
1、属性设置:项目——属性——配置属性——高级——公共语言运行时支持——选择(公共语言运行时支持(/clr))
2、属性设置:项目——属性——配置属性——VC++目录——包含目录——选择JDK二个子目录,如图
3、项目——属性——配置属性——C/C++ --> 代码生成 --> 运行库:多线程DLL(/MD) ,注意这个模式必须,否则调用C++DLL失败。
如: Exception java.lang.UnsatisfiedLinkError: dll Can't find dependent libraries
4、添加支持的h头文件到C++工程项目中头文件和资源文件中:
JDK所在目录/include/jni.h
JDK所在目录/include/win32/jni_md.h
生成的java项目类的h头文件 vip_xiaonuo_common_util_CommonFaceCore.h
5、生成的C# dll文件 PmpFaceCore.dll 拷贝到C++工程根目录里:
编辑dllmain.cpp 头文件代码如下:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include "jni.h"
#include "jni_md.h"
#include "string.h"
// 引用生成的h头文件
#include "vip_xiaonuo_common_util_CommonFaceCore.h"
//设置公共语言运行支持属性
//注:该方法需要设置公共语言运行支持属性,否则无法识别,在项目的配置属性里选择高级,公共语言那一栏要改成支持 / CLR
// 引用 字符转换库
#include <malloc.h>
#include <stdlib.h>
#include <vcclr.h>
// 引入c#的库
#using "PmpFaceCore.dll" //引用dll的文件路径,目前默认在工程根目录,也可指定
// 引入c#的命名空间
using namespace PmpFaceCore; //使用dll的命名空间
#using "System.dll"
using namespace System;
//1、声明和公共方法,在写在调用方法前面,务必注意
//1、 转换方法 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
// 2、============== 以下调用C#方法 ===================
JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_hello
(JNIEnv* env, jobject)
{
//c#中的类对象
FaceMask^ face = gcnew FaceMask();
return strTojstring(env, face->hello());
};
JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_helloworld
(JNIEnv* env, jobject)
{
//c#中的类对象
FaceMask^ face = gcnew FaceMask();
return strTojstring(env, face->helloworld());
};
JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_helloconver
(JNIEnv* env, jobject, jint a, jint b)
{
//c#中的类对象
FaceMask^ face = gcnew FaceMask();
return strTojstring(env, face->helloworld());
};
JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_faceview
(JNIEnv* env, jobject obj, jstring source)
{
//c#中的类对象
FaceMask^ face = gcnew FaceMask();
return strTojstring(env, face->faceview(jstringToStr(env, source)));
};
JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_facerecognizer
(JNIEnv* env, jobject, jstring source, jstring target) {
//c#中的类对象
FaceMask^ face = gcnew FaceMask();
return strTojstring(env, face->facerecognizer(jstringToStr(env, source), jstringToStr(env, target)));
};
注意:
1、JNI里面的类型对应关系,用到的String相关的转换函数 ,以及调用方法声明在类中的位置前后顺序,具体的可见上述代码;
2、dllmain.cpp 头文件中的引用方法 JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_helloworld 和 JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_myconversion 源于拷贝 vip_xiaonuo_common_util_CommonFaceCore.h 文件中的方法,逻辑再手工改写生成。
C++示例2:
public class JniDllTest {
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);
// 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)); }
C++示例3:
public class TestJNI {
public native void Init();
public native void isInLibrary();
public native boolean addData(String strDataBar,int nNum);
public native int getNum();
public native String getDataBar();
// CallMyLibrary.cpp : 定义控制台应用程序的入口点。 #include "jni.h" #include "hui_TestJNI.h" #include <iostream> #include <msclr\marshal_cppstd.h> #include <msclr\marshal.h> using namespace std; using namespace System; using namespace msclr::interop; //引用C#的库和命名空间 #using "MyClassLibrary.dll" using namespace MyClassLibrary; //定义一个和C#dll相关的全局变量 public ref class Globals abstract sealed { public: static MyClassLibrary::StrOperator^ myLibrary; }; //char字符串转为jstring类型 jstring charTojstring(JNIEnv* env, const char* str) { jstring rtn = 0; int slen = strlen(str); unsigned short* buffer = 0; if (slen == 0) { rtn = (env)->NewStringUTF(str); } else { int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str, slen, NULL, 0); buffer = (unsigned short*)malloc(length * 2 + 1); if (MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str, slen, (LPWSTR)buffer, length) > 0) rtn = (env)->NewString((jchar*)buffer, length); free(buffer); } return rtn; } //jstring类型转为String类型 std::string 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); std::string stemp(rtn); free(rtn); return stemp; } JNIEXPORT void JNICALL Java_hui_TestJNI_Init(JNIEnv *, jobject) { Globals::myLibrary = gcnew StrOperator(); Globals::myLibrary->isInLibrary(); } JNIEXPORT void JNICALL Java_hui_TestJNI_isInLibrary(JNIEnv *, jobject) { cout << "in" << endl; Globals::myLibrary->isInLibrary(); } JNIEXPORT jboolean JNICALL Java_hui_TestJNI_addData(JNIEnv *env, jobject, jstring strDataBar, jint nNum) { marshal_context^ context = gcnew marshal_context(); jboolean strReturn = Globals::myLibrary->addData(context->marshal_as<String^>(jstringtostring(env, strDataBar)), nNum); delete context; return strReturn; } JNIEXPORT jint JNICALL Java_hui_TestJNI_getNum(JNIEnv *, jobject) { return Globals::myLibrary->getNum(); } JNIEXPORT jstring JNICALL Java_hui_TestJNI_getDataBar(JNIEnv *env, jobject) { jstring rtnJstring = 0; std::string stdstr = marshal_as<std::string>(Globals::myLibrary->getDataBar()->ToString()); rtnJstring = charTojstring(env, stdstr.c_str()); return rtnJstring; }
最后,根据JDK的位数编译生成相应位数的dll(64位的C++编译的dll,C#的dll适用64位数的平台),将生成的C++dll和C#dll一起拷到JDK的bin目录下,Java工程运行成功编译
最后, 项目idea 调试成功,布署服务器报错误:
org.springframework.web.util.NestedServletException: Handler dispatch failed 或
org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.UnsatisfiedLinkError: no jacob-1.17-M2-x64 in java.library.path
原因是: windows的系统环境变更path 同时指定了jdk和jre的目录导致未引用到正确的dll路径, 见下:
"%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin";C:\Program Files (x86)\Common Files\Oracle\Java\javapath;%SystemRoot%\system32;%SystemRoot%;%SystemRoot%\System32\Wbem;%SYSTEMROOT%\System32\WindowsPowerShell\v1.0\;%SYSTEMROOT%\System32\OpenSSH\;C:\Windows\Microsoft.NET\Framework64\v4.0.30319;
解决办法:环境变量,保存一个目录即可。jdk为开发环境,jre为运行环境,我习惯选择jdk
附Demo : https://files.cnblogs.com/files/Fooo/TestFaceMask.rar?t=1725259402&download=true