2008-09-26 50 views
11

我有第三方.NET程序集和大型Java应用程序。我需要从Java应用程序调用.NET类库提供的方法。该程序集不支持COM。 我已经搜索的净,到目前为止我有以下:从Java调用.NET程序集:JVM崩溃

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虚拟机崩溃时调用的方法(它能够加载库):

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

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

而且,如果我修改clib.cpp调用的系统方法,一切工作正常甚至从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调用系统方法 - > clib.dll - > mscorlib.dll
  2. 我可以从CPPApp调用任何方法 - > clib.dll - > cslib.dll
  3. 我无法从Java调用任何方法 - > clib.dll - > cs lib.dll

我知道一个解决方法,使用1.上面 - 我可以使用反射装载assmebly和调用所需的方法只使用系统调用,但代码变得凌乱,我希望更好解。

我知道dotnetfromjava项目,它使用了反射方法,但不希望增加比所需更多的复杂性。但是,如果没有其他方式,我会使用类似的东西。

我也看过ikvm.net,但我的理解是它使用它自己的JVM(用C#编写)来完成这个魔术。但是,在虚拟机下运行整个Java应用程序对我来说是不行的。

谢谢。

+0

的C++代码实际上是C++/CLI吗? – Gili 2009-09-25 16:08:58

+0

是的,/ clr选项指定 – Kcats 2009-10-05 08:47:38

回答

10

好的,神秘感解决了。

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

  1. mscorlib.dll位于全局程序集缓存中,因此它可以工作。
  2. CPP应用程序exe与程序集位于同一文件夹中,因此它也可以工作。
  3. cslib.dll程序集都是在java.exe的文件夹中,在GAC中是NOR,所以它不起作用。

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

+7

哇谢谢分享这个,我一直在试图找出这个完全相同的问题。我真的想避免GAC出于各种原因,所以我找到了一种使用AssemblyResolve事件从您选择的路径手动加载程序集的方法:http://www.devcity.net/Articles/254/1/.aspx 。您必须在C++/CLI层中处理此事件,因为C#程序集尚未加载完毕。无论如何,希望这会对另一个Google员工有所帮助... – Jason 2009-10-15 18:57:35

2

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

0

我很高兴能够找到这篇文章,因为我被困住了,并且确实存在这个问题。 我想贡献一些代码,这有助于克服这个问题。 在您的Java构造函数中调用添加了解析事件的init方法。 我的经验是有必要调用init,而不是在C++代码中调用库之前,因为由于时序问题它可能会崩溃。 我已经把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); 
    } 

}