DataWizardess 2025-12-17 16:45 采纳率: 99.2%
浏览 1
已采纳

Java如何通过JNI调用C#编写的DLL?

如何在Java中通过JNI调用C#编写的DLL?由于JNI仅支持与C/C++接口交互,直接调用C# DLL不可行。常见问题是:C# DLL未暴露为原生导出函数,导致Java无法通过System.loadLibrary加载。即使使用托管C++封装或DllImport,也常因平台不匹配、函数签名错误或CLR初始化失败而崩溃。如何正确将C#代码封装为可供JNI调用的原生接口?
  • 写回答

1条回答 默认 最新

  • Airbnb爱彼迎 2025-12-17 16:51
    关注

    如何在Java中通过JNI调用C#编写的DLL?

    Java Native Interface(JNI)是Java平台与本地代码交互的标准机制,但其本质仅支持与C/C++等原生语言编写的动态链接库进行通信。由于C#编写的DLL属于.NET托管程序集,无法被JNI直接加载或调用。因此,实现Java通过JNI调用C# DLL的核心挑战在于:如何将托管代码暴露为原生可调用接口。

    1. 问题本质分析

    C# DLL默认以IL(Intermediate Language)形式存在,运行在CLR(Common Language Runtime)之上,不具备原生导出函数表(Export Table),导致Java的System.loadLibrary()无法识别和绑定符号。即使使用[DllImport]特性,也只能从原生代码调用托管代码,而非反向操作。

    常见失败场景包括:

    • 尝试直接加载C# DLL导致UnsatisfiedLinkError
    • 使用C++/CLI封装时因目标平台不匹配(x86/x64/ARM)引发崩溃
    • 函数签名不一致(如字符串编码、调用约定__stdcall vs __cdecl)
    • CLR未正确初始化,导致托管对象访问失败
    • 跨AppDomain资源管理混乱,引发内存泄漏或GC异常

    2. 解决路径总览

    要实现Java → JNI → C# 的调用链,必须引入中间层。以下是可行的技术路径:

    方案技术栈适用场景优点缺点
    托管C++封装(C++/CLI)C++/CLI + .NET FrameworkWindows平台,已有C++经验直接桥接托管与原生,性能高仅限Windows,需VC++工具链
    COM组件封装C# COM可见 + JNI调用ole32.dll遗留系统集成跨语言标准,Java可通过JACOB调用配置复杂,权限要求高
    原生导出(Unmanaged Exports)DllExport工具 + C#方法标记轻量级导出单个函数无需额外中间DLL依赖第三方工具,维护性差
    进程间通信(IPC)命名管道、gRPC、REST API跨平台、解耦架构灵活性强,支持远程调用延迟较高,需额外服务进程

    3. 推荐方案:C++/CLI 中间层封装

    该方案是目前最成熟且性能最优的实现方式。其核心思想是创建一个C++/CLI项目,作为“胶水层”,对外提供原生C风格接口,对内调用C#类库。

    实现步骤如下:

    1. 编写C#类库并确保类和方法为public且可实例化
    2. 创建C++/CLI动态库项目(Visual Studio中选择“CLR Class Library”)
    3. 在C++/CLI中引用C# DLL,并使用gcroot管理托管对象生命周期
    4. 定义extern "C"函数,避免C++名称修饰
    5. 编译生成原生可加载DLL(.dll文件)
    6. Java端编写JNI头文件并实现本地方法绑定
    7. 使用System.loadLibrary()加载C++/CLI生成的DLL

    4. 代码示例:C++/CLI 封装层

    
    // NativeWrapper.h
    #pragma once
    extern "C" {
        __declspec(dllexport) int __cdecl CallCSharpAdd(int a, int b);
    }
        
    
    // NativeWrapper.cpp
    #include "NativeWrapper.h"
    #using "ManagedLibrary.dll" // 引用C# DLL
    
    using namespace System;
    using namespace ManagedLibrary;
    
    static gcroot<Calculator^> calc = gcnew Calculator();
    
    extern "C" int __cdecl CallCSharpAdd(int a, int b) {
        try {
            return calc->Add(a, b);
        } catch (Exception^ ex) {
            Console::WriteLine(ex->Message);
            return -1;
        }
    }
        

    5. Java端JNI调用实现

    
    public class CSharpBridge {
        static {
            System.loadLibrary("NativeWrapper"); // 加载C++/CLI生成的DLL
        }
    
        public native int callCSharpAdd(int a, int b);
    
        public static void main(String[] args) {
            CSharpBridge bridge = new CSharpBridge();
            int result = bridge.callCSharpAdd(5, 7);
            System.out.println("Result from C#: " + result);
        }
    }
        

    6. 构建与部署关键点

    为确保稳定运行,需注意以下细节:

    • 平台一致性:Java JVM、C++/CLI DLL、C# DLL必须均为x64或x86,不可混用
    • .NET版本匹配:C++/CLI项目需针对目标机器安装的.NET Framework版本编译
    • DLL依赖拷贝:确保C# DLL与C++/CLI DLL位于同一目录,或注册到GAC
    • 调用约定:使用__cdecl避免栈破坏,Java JNI默认使用此约定
    • 异常传播:C#异常不可跨越原生边界,需在C++/CLI层捕获并转换为错误码
    • 生命周期管理:使用gcroot自动管理CLR对象,避免内存泄漏
    • 调试支持:启用混合模式调试(Mixed-Mode Debugging)以同时调试原生与托管代码

    7. 替代方案:Unmanaged Exports 工具

    对于简单函数导出,可使用开源工具UnmanagedExports(由Robert Giesecke开发),允许C#方法直接标记为原生导出:

    
    [DllExport("Add", CallingConvention = CallingConvention.Cdecl)]
    public static int Add(int a, int b)
    {
        return a + b;
    }
        

    该方法无需C++/CLI中间层,但要求目标环境有对应.NET运行时,且不支持复杂对象传递。

    8. 调用流程图解

    graph LR A[Java Application] -- JNI --> B[C++/CLI DLL] B -- P/Invoke or Direct Call --> C[C# Managed DLL] C -- CLR Execution --> D[.NET Runtime] B -- Native Export --> A style A fill:#f9f,stroke:#333 style B fill:#bbf,stroke:#333 style C fill:#f96,stroke:#333 style D fill:#6f9,stroke:#333

    9. 常见错误与排查策略

    现象可能原因解决方案
    UnsatisfiedLinkErrorDLL未找到或符号不存在检查路径、导出函数名是否正确,使用Dependency Walker验证
    Access Violation调用约定不匹配或参数类型错误统一使用__cdecl,检查int/long映射
    CLR not initialized宿主未启动.NET运行时确保C++/CLI项目启用/clr编译
    Method not foundC#方法未public或静态确认访问修饰符和静态属性
    GC collected object托管对象被提前释放使用gcroot持久化引用
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 12月18日
  • 创建了问题 12月17日