明天的明天 永远的永远 未知的一切 我与你一起承担 ??

是非成败转头空 青山依旧在 几度夕阳红 。。。
  博客园  :: 首页  :: 管理

JAVA通过JNI调用C#dll方法说明(包含示例)

Posted on 2024-08-21 22:00  且行且思  阅读(336)  评论(0编辑  收藏  举报

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_helloworldJNIEXPORT 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