Вопрос

У меня есть сторонняя сборка .NET и большое приложение Java.Мне нужно вызвать методы, предоставляемые библиотекой классов .NET, из приложения Java.Сборка не поддерживает COM.Я порылся в сети и на данный момент имею следующее:

Код С# (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

Однако если я создам простое приложение 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-приложения под виртуальной машиной для меня невозможен.

Спасибо.

Это было полезно?

Решение

Хорошо, загадка раскрыта.

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

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

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

Другие советы

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

Вы смотрели ikvm.NET, который позволяет осуществлять вызовы между кодом .NET и Java?

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

}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top