JAVA通过JNI调用C#dll方法说明(包含示例)
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文件生成
建立一个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)
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" // 引用 字符转换库 #include <malloc.h> #include <stdlib.h> #include <vcclr.h> //设置公共语言运行支持属性 //注:该方法需要设置公共语言运行支持属性,否则无法识别,在项目的配置属性里选择高级,公共语言那一栏要改成支持 / CLR // 引入c#的库 #using "PmpFaceCore.dll" //引用dll的文件路径 // 引入c#的命名空间 using namespace PmpFaceCore; //使用dll的命名空间 // 其他引用 #include <msclr\marshal_cppstd.h> #include <msclr\marshal.h> #include <malloc.h> #include <stdlib.h> #include <vcclr.h> #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; using namespace std; using namespace msclr::interop; //定义一个和C#dll相关的全局变量 public ref class Globals abstract sealed { public: static PmpFaceCore::Hello^ myLibrary; }; //声明和公共方法,在写在调用方法前面,务必注意 //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 ————————— //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; } // ———————————————— //2、============== 以下调用C#方法 =================== //vip_xiaonuo_common_util_CommonFaceCore.h 拷贝实现方法 /* * Class: vip_xiaonuo_common_util_CommonFaceCore * Method: helloWorld * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_helloworld (JNIEnv* env, jobject) { //return Globals::myLibrary->helloworld(); //c#中的类对象 Hello^ o = gcnew Hello(); return strTojstring(env, o->helloworld()); }; /* * Class: vip_xiaonuo_common_util_CommonFaceCore * Method: myConversion * Signature: (II)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_vip_xiaonuo_common_util_CommonFaceCore_myconversion (JNIEnv* env, jobject, jint a, jint b) { //return Globals::myLibrary->helloworld(); jstring rtnJstring = 0; //c#中的类对象 Hello^ o = gcnew Hello(); return strTojstring(env, o->myconversion(a, b)); };
注意:
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工程运行成功编译