Вызов сборки.NET из Java: сбой JVM
У меня есть сторонняя сборка.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 при вызове метода (он может загрузить библиотеку):
# # Непредвиденная ошибка была обнаружена Java Runtime Environment: # # Внутренняя ошибка (0xe0434f4d), pid=3144, tid=3484 # # Java VM: клиентская виртуальная машина Java HotSpot(TM) (смешанный режим 10.0-b19, совместное использование windows-x86) # Проблемная рамка: # C [kernel32.dll+0x22366] # ... Фреймы Java: (J= скомпилированный код Java, j= интерпретированный, Vv= код VM) 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");
}
Упаковать:
- Я в состоянии вызвать системные методы из Java -> clib.dll -> mscorlib.dll
- Я в состоянии вызвать любые методы из CPPApp -> clib.dll -> cslib.dll
- Я не в состоянии вызвать любые методы из Java -> clib.dll -> cslib.dll
Я знаю обходной путь, который использует 1. выше - я могу использовать отражение, чтобы загрузить ассемблер и вызывать нужные методы, используя только системные вызовы, но код становится грязным, и я надеюсь на лучшее решение.
Я знаю о проекте dotnetfromjava, который использует метод отражения, но предпочитаю не добавлять больше сложности, чем необходимо. Я буду использовать что-то вроде этого, если нет другого пути, однако.
Я также посмотрел на ikvm.net, но, насколько я понимаю, он использует свою собственную JVM (написанную на C#), чтобы творить чудеса. Тем не менее, запуск всего приложения Java под его виртуальной машиной для меня не вариант.
Благодарю.
4 ответа
ОК, загадка раскрыта
Сбой JVM вызван необработанным System.IO.FileNotFoundException. Исключение вызвано тем, что сборка.NET ищется в папке, где находится вызывающий exe-файл.
- Файл mscorlib.dll находится в глобальном кэше сборок, поэтому он работает.
- Исполняемый файл приложения CPP находится в той же папке, что и сборка, поэтому он также работает.
- Сборка cslib.dll находится НИКОГДА в папке java.exe, NOR в GAC, поэтому она не работает.
Кажется, мой единственный вариант - установить сборку.NET в GAC (сторонняя библиотека DLL имеет строгое имя).
Вы смотрели на ikvm.NET, который позволяет звонки между.NET и Java-кодом?
Я был так рад найти эту статью, так как застрял и столкнулся именно с этой проблемой. Я хочу внести некоторый код, который помогает преодолеть эту проблему. В вашем Java-конструкторе вызовите метод init, который добавляет событие resolve. По моему опыту, необходимо вызывать 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);
}
}