Domanda

Ho un .NET Assembly di terze parti e una grande applicazione Java. Devo chiamare i mothod forniti dalla libreria di classi .NET dall'applicazione Java. L'assembly non è abilitato per COM. Ho cercato in rete e finora ho il seguente:

Codice C # (cslib.cs):

using System;

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

compilato con (usando .net 3.5, ma lo stesso succede quando si usa 2.0):

csc /target:library cslib.cs

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

compilato con (usando gli strumenti di VC 2008, ma lo stesso accade quando si usano gli strumenti del 2003):

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

Codice Java (CallCS.java):

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

Quando provo a eseguire la classe java, la VM Java si arresta in modo anomalo mentre richiama il metodo (è in grado di caricare la libreria):

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

Tuttavia, se creo una semplice applicazione cpp che carica clib.dll e chiama la funzione esportata Java_CallCS_callCS, tutto è OK. Ho provato questo su entrambi gli ambienti x86 e x64 e il risultato è lo stesso. Non ho provato altre versioni di Java, ma ho bisogno del codice per funzionare su 1.5.0.

Inoltre, se modifico clib.cpp per chiamare solo i metodi di sistema, tutto funziona bene anche da 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");
}

Per concludere:

  1. Sono in grado di chiamare i metodi di sistema da Java - > clib.dll - > mscorlib.dll
  2. Sono in grado di chiamare qualsiasi metodo da CPPApp - > clib.dll - > cslib.dll
  3. NON POSSO chiamare alcun metodo da Java - > clib.dll - > cslib.dll

Sono a conoscenza di una soluzione alternativa che utilizza 1. sopra: posso usare la riflessione per caricare in modo assiemabile e invocare i metodi desiderati utilizzando solo le chiamate di sistema, ma il codice diventa disordinato e spero in una soluzione migliore.

Conosco il progetto dotnetfromjava, che utilizza il metodo di riflessione, ma preferisco non aggiungere più complessità del necessario. Userò qualcosa del genere se non c'è altro modo, tuttavia.

Ho anche guardato ikvm.net, ma la mia comprensione è che usa la propria JVM (scritta in C #) per fare la magia. Tuttavia, eseguire l'intera applicazione Java sotto la sua VM non è un'opzione per me.

Grazie.

È stato utile?

Soluzione

OK, il mistero è risolto.

L'arresto anomalo di JVM è causato da System.IO.FileNotFoundException non gestito. L'eccezione viene generata perché l'assembly .NET viene cercato nella cartella in cui risiede il file exe chiamante.

  1. mscorlib.dll si trova nella Global Assembly Cache, quindi funziona.
  2. Il file exe dell'applicazione CPP si trova nella stessa cartella dell'assembly, quindi funziona anche.
  3. L'assembly cslib.dll non è NESSUNO nella cartella di java.exe, NOR nel GAC, quindi non funziona.

Sembra che la mia unica opzione sia quella di installare l'assembly .NET in GAC (la dll di terze parti ha un nome sicuro).

Altri suggerimenti

Guarda jni4net , farà il duro lavoro per te.

Hai guardato ikvm.NET, che consente le chiamate tra il codice .NET e Java?

Sono stato così felice di trovare questo articolo da quando mi sono bloccato e ho avuto esattamente quel problema. Voglio contribuire con del codice, che aiuta a superare questo problema. Nel tuo costruttore Java chiama il metodo init, che aggiunge l'evento di risoluzione. La mia esperienza è che è necessario chiamare init NON appena prima della chiamata nella libreria nel codice c ++, poiché a causa di problemi di temporizzazione potrebbe comunque bloccarsi. Ho inserito la chiamata init nel mio costruttore di classi Java per mappare le chiamate JNI, che funziona alla grande.

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


    }
}

Codice 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

}

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

}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top