2008-09-26 19 views
11

Tengo un ensamblado de .NET de terceros y una gran aplicación de Java. Necesito llamar a mothods proporcionados por la biblioteca de clases .NET desde la aplicación Java. El ensamblaje no está habilitado para COM. He buscado la red y hasta ahora tengo el siguiente:Llamar al ensamblado de .NET desde Java: bloqueos de JVM

código C# (cslib.cs):

using System; 

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

compilado con (utilizando .NET 3.5, pero lo mismo sucede cuando se utiliza 2.0) código

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

compilado con (utilizando VC 2008 herramientas, pero º e igual ocurre cuando se utilizan herramientas 2003):

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

código Java (CallCS.java):

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

Cuando trato de ejecutar la clase de Java, se bloquea la máquina virtual de Java mientras se invoca el método (es capaz de cargar la biblioteca):

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

Sin embargo, si se crea una aplicación CPP normal que carga clib.dll y llama a los Java_CallCS_callCS función exportada, todo está bien. He intentado esto tanto en entornos x86 como x64 y el resultado es el mismo. No he probado otras versiones de Java, pero necesito el código para ejecutar en 1.5.0.

Por otra parte, si modifico clib.cpp a llamar a los métodos del sistema todo funciona bien incluso de 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"); 
} 

Para terminar:

  1. soy capaz de llamar a los métodos del sistema de Java -> clib.dll -> mscorlib.dll
  2. PUEDO llamar a cualquier método desde CPPApp -> clib.dll -> cslib.dll
  3. No puedo llamar a ningún método desde Java -> clib.dll - > cs lib.dll

Soy consciente de una solución alternativa que utiliza 1. anterior - Puedo usar la reflexión para cargar el assmebly e invocar los métodos deseados utilizando solo llamadas al sistema, pero el código se vuelve complicado y espero una mejor solución.

Conozco el proyecto dotnetfromjava, que utiliza el método de reflexión, pero prefiere no agregar más complejidad de la necesaria. Usaré algo así si no hay otra manera, sin embargo.

También he consultado ikvm.net, pero tengo entendido que usa su propia JVM (escrita en C#) para hacer la magia. Sin embargo, ejecutar toda la aplicación Java en su máquina virtual no es una opción para mí.

Gracias.

+0

El código C++ ++ es en realidad C++/CLI ¿verdad? – Gili

+0

Sí, se especifica la opción/clr – Kcats

Respuesta

10

OK, el misterio está resuelto.

El bloqueo de JVM es causado por System.IO.FileNotFoundException no administrado. La excepción se produce porque se busca el ensamblado .NET en la carpeta donde reside el archivo ejecutable.

  1. El mscorlib.dll se encuentra en la caché de ensamblados global, por lo que funciona.
  2. El exe de la aplicación CPP está en la misma carpeta que el ensamblaje, por lo que también funciona.
  3. El ensamblado cslib.dll NO SE ENCUENTRA en la carpeta de java.exe ni en el GAC, por lo que no funciona.

Parece que mi única opción es instalar el ensamblado .NET en GAC (el dll de terceros tiene un nombre seguro).

+7

Gracias por compartir esto, he estado tratando de resolver este mismo problema. Realmente quería evitar el GAC por varias razones, así que encontré una manera de cargar manualmente los ensamblajes desde la ruta de su elección usando el evento AssemblyResolve: http://www.devcity.net/Articles/254/1/.aspx . Tienes que manejar este evento en la capa C++/CLI ya que los ensamblajes C# no se habrán cargado todavía. De todos modos, con suerte esto será útil para otro Googler ... – Jason

+0

Gracias por compartir esto, de hecho terminé usando exactamente el evento AssemblyResolve, pero olvidé actualizar la respuesta. – Kcats

2

¿Ha mirado a ikvm.NET, que permite llamadas entre .NET y el código de Java?

0

Estaba tan contento de encontrar este artículo porque me quedé atascado y tenía exactamente ese problema. Quiero contribuir con algún código, lo que ayuda a superar este problema. En su constructor de Java, llame al método init, que agrega el evento de resolución. Mi experiencia es necesaria para llamar a init NO justo antes de la llamada a su biblioteca en código de C++, ya que debido a problemas de tiempo puede bloquearse de todos modos. He puesto la llamada init en el constructor de mi clase java para mapear las llamadas JNI, que funciona muy bien.

//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ódigo

código
// 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); 
    } 

} 
Cuestiones relacionadas