Вызов сборки .NET из Java: сбой JVM - PullRequest
11 голосов
/ 26 сентября 2008

У меня есть сторонняя сборка .NET и большое Java-приложение. Мне нужно вызывать методы, предоставляемые библиотекой классов .NET из приложения Java. Сборка не поддерживает 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 так, чтобы он вызывал только системные методы, все отлично работает даже с 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 -> cslib.dll

Мне известен обходной путь, который использует 1. выше - я могу использовать отражение, чтобы загрузить ассемблер и вызывать нужные методы, используя только системные вызовы, но код запутывается, и я надеюсь на лучшее решение.

Я знаю о проекте dotnetfromjava, который использует метод отражения, но предпочитаю не добавлять больше сложности, чем необходимо. Я буду использовать что-то вроде этого, если нет другого пути, однако.

Я также посмотрел на ikvm.net, но, насколько я понимаю, он использует свою собственную JVM (написанную на C #), чтобы творить чудеса. Однако запуск всего Java-приложения под его виртуальной машиной для меня не вариант.

Спасибо.

Ответы [ 4 ]

10 голосов
/ 26 сентября 2008

ОК, загадка раскрыта.

Сбой JVM вызван необработанным исключением System.IO.FileNotFoundException. Исключение вызвано тем, что сборка .NET ищется в папке, где находится вызывающий exe-файл.

  1. Файл mscorlib.dll находится в глобальном кэше сборок, поэтому он работает.
  2. exe приложения CPP находится в той же папке, что и сборка, поэтому он также работает.
  3. Сборка cslib.dll отсутствует в папке java.exe, NOR в GAC, поэтому она не работает.

Кажется, мой единственный вариант - установить сборку .NET в GAC (сторонний dll имеет строгое имя).

4 голосов
/ 31 октября 2009

Посмотрите на jni4net , это сделает тяжелую работу за вас.

2 голосов
/ 26 сентября 2008

Вы смотрели на ikvm.NET, который разрешает вызовы между .NET и Java-кодом?

0 голосов
/ 09 июля 2013

Я был так рад найти эту статью, так как застрял и имел именно эту проблему. Я хочу внести некоторый код, который помогает преодолеть эту проблему. В вашем конструкторе Java вызовите метод init, который добавляет событие resolv. По моему опыту, необходимо вызывать init NOT непосредственно перед вызовом в вашу библиотеку в коде 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);
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...