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

11

我有一个第三方的.NET程序集和一个大型Java应用程序。我需要从Java应用程序中调用.NET类库提供的方法。该程序集没有启用COM。

我已经在网络上搜索过了,目前我有以下内容:

C#代码(cslib.cs):

using System;

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

编译时使用了 .NET 3.5(但使用 2.0 版本时同样会出现此问题):

csc /target:library cslib.cs

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();
}

使用VC 2008工具编译(但使用2003工具时情况相同):

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

Java代码(CallCS.java):

class CallCS {
    static {
       System.loadLibrary("clib");
    }
    private static native void callCS();
    public static void main(String[] args) {
        callCS();
    }
}
当我尝试运行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
然而,如果我创建一个简单的cpp应用程序,加载clib.dll并调用导出的函数Java_CallCS_callCS,则一切正常。 我已经在x86和x64环境下尝试过这种情况,结果相同。我没有尝试其他版本的Java,但我需要代码在1.5.0上运行。
此外,如果我修改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");
}

总结:

  1. 我能够从Java中调用System方法 -> clib.dll -> mscorlib.dll
  2. 我能够从CPPApp中调用任何方法 -> clib.dll -> cslib.dll
  3. 我无法从Java中调用任何方法 -> clib.dll -> cslib.dll

我知道一种使用上述第一种方法的解决方法-我可以使用反射来加载程序集,并仅使用系统调用调用所需的方法,但代码会变得混乱,我希望有更好的解决方案。

我知道dotnetfromjava项目,它使用反射方法,但我不想增加不必要的复杂性。如果没有其他方法,我将使用类似的解决方案。

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

谢谢。


这段 C++ 代码实际上是 C++/CLI 吗? - Gili
是的,已指定/clr选项。 - Kcats
4个回答

10

好的,谜底已经揭晓。

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

  1. mscorlib.dll位于全局程序集缓存中,所以它能正常工作。
  2. C++应用程序exe文件与程序集位于同一文件夹中,所以它也可以正常工作。
  3. cslib.dll程序集既不在java.exe的文件夹中,也不在全局程序集缓存中,因此它无法工作。

看起来,我唯一的选择是将.NET程序集安装在全局程序集缓存中(第三方dll确实有强名称)。


7
哇,感谢分享这个信息,我一直在尝试解决同样的问题。出于各种原因,我真的想避免使用GAC,所以我找到了一种方法,可以使用AssemblyResolve事件从您选择的路径手动加载程序集:http://www.devcity.net/Articles/254/1/.aspx。由于C#程序集尚未加载,所以必须在C++/CLI层处理此事件。无论如何,希望这对其他谷歌搜索者有所帮助... - Jason
谢谢分享这个,事实上我最终使用了AssemblyResolve事件,但是忘记更新答案了。 - Kcats

4

看看jni4net,它将为您完成繁重的工作。


哇,很棒!谢谢你提供的链接。 - dthorpe

2

你有没有看过ikvm.NET呢?它可以在.NET和Java代码之间进行调用。


0
我很高兴找到了这篇文章,因为我遇到了同样的问题而卡住了。 我想贡献一些代码,帮助解决这个问题。 在你的Java构造函数中调用init方法,添加解析事件。 我的经验是,在调用C++代码库之前,不仅需要在时机上调用init,因为由于时间问题,它可能仍然会崩溃。 我已经将init调用放入了我的Java类构造函数中,以映射JNI调用,效果非常好。
    //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++ 代码

    // 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 代码

    /***
    ** 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);
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接