C# 从 Java 调用 .NET 程序集:JVM 崩溃

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/138355/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 15:07:36  来源:igfitidea点击:

Calling .NET assembly from Java: JVM crashes

提问by Kcats

I have a third party .NET Assembly and a large Java application. I need to call mothods provided by the .NET class library from the Java application. The assembly is not COM-enabled. I have searched the net and so far i have the following:

我有一个第三方 .NET 程序集和一个大型 Java 应用程序。我需要从 Java 应用程序调用 .NET 类库提供的方法。该程序集未启用 COM。我已经搜索了网络,到目前为止我有以下内容:

C# code (cslib.cs):

C# 代码 (cslib.cs):

using System;

namespace CSLib
{
    public class CSClass
    {
        public static void SayHi()
        {
            System.Console.WriteLine("Hi");
        }
    }
}

compiled with (using .net 3.5, but the same happens when 2.0 is used):

编译(使用 .net 3.5,但使用 2.0 时会发生同样的情况):

csc /target:library cslib.cs

C++ code (clib.cpp):

C++ 代码(clib.cpp):

#include <jni.h>
#using <CSLib.dll>

using namespace CSLib;

extern "C" _declspec(dllexport) void Java_CallCS_callCS(JNIEnv* env, jclass cls) {
    CSLib::CSClass::SayHi();
}

compiled with (using VC 2008 tools, but the same happens when 2003 tools are used):

编译(使用 VC 2008 工具,但使用 2003 工具时会发生同样的情况):

cl /clr /LD clib.cpp
mt -manifest clib.dll.manifest -outputresource:clib.dll;2

Java code (CallCS.java):

Java 代码(CallCS.java):

class CallCS {
    static {
       System.loadLibrary("clib");
    }
    private static native void callCS();
    public static void main(String[] args) {
        callCS();
    }
}

When I try to run the java class, the Java VM crashes while invoking the method (it is able to load the library):

当我尝试运行 java 类时,Java VM 在调用该方法时崩溃(它能够加载库):

#
# An unexpected error has been detected by Java Runtime Environment:
#
#  Internal Error (0xe0434f4d), pid=3144, tid=3484
#
# Java VM: Java HotSpot(TM) Client VM (10.0-b19 mixed mode, sharing windows-x86)
# Problematic frame:
# C  [kernel32.dll+0x22366]
#
...
Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)
j  CallCS.callCS()V+0
j  CallCS.main([Ljava/lang/String;)V+0
v  ~StubRoutines::call_stub

However, if I create a plain cpp application that loads clib.dll and calls the exported function Java_CallCS_callCS, everything is OK. I have tried this on both x86 and x64 environments and the result is the same. I have not tried other versions of Java, but I need the code to run on 1.5.0.

但是,如果我创建一个加载 clib.dll 并调用导出函数 Java_CallCS_callCS 的普通 cpp 应用程序,则一切正常。我在 x86 和 x64 环境中都试过这个,结果是一样的。我还没有尝试过其他版本的 Java,但我需要代码在 1.5.0 上运行。

Moreover, if I modify clib.cpp to call only System methods everything works fine even from Java:

此外,如果我修改 clib.cpp 以仅调用 System 方法,即使从 Java 也一切正常:

#include <jni.h>
#using <mscorlib.dll>

using namespace System;

extern "C" _declspec(dllexport) void Java_CallCS_callCS(JNIEnv* env, jclass cls) {
    System::Console::WriteLine("It works");
}

To wrap up:

总结:

  1. I am ABLE to call System methods from Java -> clib.dll -> mscorlib.dll
  2. I am ABLE to call any methods from CPPApp -> clib.dll -> cslib.dll
  3. I am UNABLE to call any methods from Java -> clib.dll -> cslib.dll
  1. 我能够从 Java -> clib.dll -> mscorlib.dll 调用系统方法
  2. 我可以从 CPPApp -> clib.dll -> cslib.dll 调用任何方法
  3. 我无法从 Java -> clib.dll -> cslib.dll 调用任何方法

I am aware of a workaround that uses 1. above - I can use reflection to load the assmebly and invoke desired methods using only System calls, but the code gets messy and I am hoping for a better solution.

我知道使用 1. 上面的解决方法 - 我可以使用反射来加载 assmebly 并仅使用系统调用来调用所需的方法,但代码变得混乱,我希望有更好的解决方案。

I know about dotnetfromjava project, which uses the reflection method, but prefer not to add more complexity than needed. I'll use something like this if there is no other way, however.

我知道 dotnetfromjava 项目,它使用反射方法,但不想增加比需要更多的复杂性。但是,如果没有其他方法,我将使用类似的方法。

I have looked at ikvm.net also, but my understanding is that it uses its own JVM (written in C#) to do the magic. However, running the entire Java application under its VM is no option for me.

我也看过 ikvm.net,但我的理解是它使用自己的 JVM(用 C# 编写)来实现魔法。但是,在其 VM 下运行整个 Java 应用程序对我来说不是一个选择。

Thanks.

谢谢。

采纳答案by Kcats

OK, the mystery is solved.

好了,谜团解开了。

The JVM crash is caused by unhandled System.IO.FileNotFoundException. The exception is thrown because the .NET assembly is searched in the folder where the calling exe file resides.

JVM 崩溃是由未处理的 System.IO.FileNotFoundException 引起的。抛出异常是因为在调用 exe 文件所在的文件夹中搜索了 .NET 程序集。

  1. The mscorlib.dll is in the Global Assembly Cache, so it works.
  2. The CPP application exe is in the same folder as the assembly, so it works also.
  3. The cslib.dll assembly is NEITHER in the folder of java.exe, NOR in the GAC, so it doesn't work.
  1. mscorlib.dll 位于全局程序集缓存中,因此它可以工作。
  2. CPP 应用程序 exe 与程序集位于同一文件夹中,因此它也可以工作。
  3. cslib.dll 程序集不在 java.exe 文件夹中,也不在 GAC 中,因此它不起作用。

It seems my only option is to install the .NET assembly in GAC (the third-party dll does have a strong name).

看来我唯一的选择是在 GAC 中安装 .NET 程序集(第三方 dll 确实有一个强名称)。

回答by cruizer

Have you looked at ikvm.NET, which allows calls between .NET and Java code?

你看过 ikvm.NET,它允许 .NET 和 Java 代码之间的调用吗?

回答by user200245

Look at jni4net, it will do the hard work for you.

看看jni4net,它会为你做艰苦的工作。

回答by dusselduck22

I was so glad to find this article since I got stuck and had exactly that problem. I want to contribute some code, which helps to overcome this problem. In your Java constructor call the init method, which adds the resolve event. My experience it is necessary to call init NOT just before the call into your library in c++ code, since due to timing problems it may crash nonetheless. I've put the init call into my java class constructor of mapping the JNI calls, which works great.

我很高兴找到这篇文章,因为我遇到了这个问题。我想贡献一些代码,这有助于克服这个问题。在您的 Java 构造函数中调用 init 方法,该方法添加了解析事件。根据我的经验,有必要在调用 C++ 代码中的库之前调用 init ,因为由于时间问题,它仍然可能会崩溃。我已将 init 调用放入映射 JNI 调用的 java 类构造函数中,效果很好。

    //C# code
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Security.Permissions;
using System.Runtime.InteropServices;

namespace JNIBridge
{
    public class Temperature
    {

        [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode | SecurityPermissionFlag.Assertion | SecurityPermissionFlag.Execution)]
        [ReflectionPermission(SecurityAction.Assert, Unrestricted = true)]
        [FileIOPermission(SecurityAction.Assert, Unrestricted = true)]

        public static double toFahrenheit(double value)
        {
            return (value * 9) / 5 + 32;
        }

        [SecurityPermission(SecurityAction.Assert, Flags = SecurityPermissionFlag.UnmanagedCode | SecurityPermissionFlag.Assertion | SecurityPermissionFlag.Execution)]
        [ReflectionPermission(SecurityAction.Assert, Unrestricted = true)]
        [FileIOPermission(SecurityAction.Assert, Unrestricted = true)]

        public static double toCelsius(double value)
        {
            return (value - 32) * 5 / 9; 
        }


    }
}

C++ Code

C++代码

    // C++ Code

#include "stdafx.h"

#include "JNIMapper.h"
#include "DotNet.h"
#include "stdio.h"
#include "stdlib.h"

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     DotNet
 * Method:    toFahrenheit
 * Signature: (D)D
 */

static bool initialized = false;
using namespace System;
using namespace System::Reflection;

/*** 
 This is procedure is always needed when the .NET dll's arent in the actual directory of the calling exe!!!
 It loads the needed assembly from a predefined path, if found in the directory and returns the assembly.
*/

Assembly ^OnAssemblyResolve(Object ^obj, ResolveEventArgs ^args)
{
    //System::Console::WriteLine("In OnAssemblyResolve");
#ifdef _DEBUG
            /// Change to your .NET DLL paths here
    String ^path = gcnew String("d:\WORK\JNIBridge\x64\Debug");
#else
    String ^path = gcnew String(_T("d:\WORK\JNIBridge\x64\Release"));
#endif
    array<String^>^ assemblies =
        System::IO::Directory::GetFiles(path, "*.dll");
    for (long ii = 0; ii < assemblies->Length; ii++) {
        AssemblyName ^name = AssemblyName::GetAssemblyName(assemblies[ii]);
        if (AssemblyName::ReferenceMatchesDefinition(gcnew AssemblyName(args->Name), name)) {
        //  System::Console::WriteLine("Try to resolve "+ name);
            Assembly ^a = Assembly::Load(name);
            //System::Console::WriteLine("Resolved "+ name);
            return a;
        }
    }
    return nullptr;
}

/**
 This procedure adds the Assembly resolve event handler
*/
void AddResolveEvent()
{
    AppDomain::CurrentDomain->AssemblyResolve +=
        gcnew ResolveEventHandler(OnAssemblyResolve);
}
/*
 * Class:     DotNet
 * Method:    init
 * Signature: ()Z
 */
JNIEXPORT jboolean JNICALL Java_DotNet_init
  (JNIEnv *, jobject)

{
    printf("In init\n");    
    AddResolveEvent();  
    printf("init - done.\n");   
    return true;

}

/*
 * Class:     DotNet
 * Method:    toFahrenheit
 * Signature: (D)D
 */

JNIEXPORT jdouble JNICALL Java_DotNet_toFahrenheit
  (JNIEnv * je, jobject jo, jdouble value)
{
    printf("In Java_DotNet_toFahrenheit\n");  

      double result = 47;

      try{        
          result = JNIBridge::Temperature::toFahrenheit(value);
      } catch (...){
          printf("Error caught");
      }
      return result;
}

/*
 * Class:     DotNet
 * Method:    toCelsius
 * Signature: (D)D
 */
JNIEXPORT jdouble JNICALL Java_DotNet_toCelsius
  (JNIEnv * je, jobject jo , jdouble value){

      printf("In Java_DotNet_toCelsius\n");

      double result = 11;

      try{

          result = JNIBridge::Temperature::toCelsius(value);
      } catch (...){
          printf("Error caught");
      }

      return result;
}


#ifdef __cplusplus

}

Java code

Java代码

    /***
    ** Java class file
    **/
public class DotNet {    
    public native double toFahrenheit (double d);
    public native double toCelsius (double d);
    public native boolean init();

    static {
        try{            
            System.loadLibrary("JNIMapper");
        } catch(Exception ex){
            ex.printStackTrace();
        }
    }        

    public DotNet(){
        init();
    }

    public double fahrenheit (double v) {
        return toFahrenheit(v);
    }

    public double celsius (double v) {
        return toCelsius(v);
    }

}