2008-09-26 51 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); 
    } 

}