我有第三方.NET程序集和一个大型Java应用程序。我需要从Java应用程序调用.NET类库提供的方法。程序集未启用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检测到意外错误:
#
#Internal Error(0xe0434f4d),pid = 3144,tid = 3484
#
#Java VM:Java HotSpot(TM)客户端VM(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
但是,如果我创建一个加载clib.dll并调用导出函数Java_CallCS_callCS的普通cpp应用程序,一切正常。
我在x86和x64环境中尝试了这个,结果是一样的。我没有尝试过其他版本的Java,但我需要在1.5.0上运行代码。
此外,如果我修改clib.cpp只调用System方法,即使从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.上面的解决方法 - 我可以使用反射加载assmeble并仅使用系统调用调用所需的方法,但代码变得混乱,我希望有更好的解决方案。
我知道dotnetfromjava项目,它使用反射方法,但不希望增加比所需更多的复杂性。但是,如果没有别的办法,我会用这样的东西。
我也看了ikvm.net,但我的理解是它使用自己的JVM(用C#编写)来实现魔力。但是,在我的VM下运行整个Java应用程序是没有选择的。
谢谢。
好的,这个谜就解决了。
JVM崩溃是由未处理的System.IO.FileNotFoundException引起的。抛出异常是因为在调用exe文件所在的文件夹中搜索.NET程序集。
- mscorlib.dll位于全局程序集缓存中,因此可以正常运行。
- CPP应用程序exe与程序集位于同一文件夹中,因此它也可以。
- cslib.dll程序集在java.exe的文件夹中是NEITHER,在GAC中是NOR,因此它不起作用。
似乎我唯一的选择是在GAC中安装.NET程序集(第三方dll确实有一个强名称)。
您是否看过ikvm.NET,它允许在.NET和Java代码之间进行调用?
我很高兴找到这篇文章,因为我遇到了问题。
我想贡献一些代码,这有助于克服这个问题。
在Java构造函数中调用init方法,该方法添加resolve事件。
我的经验是,有必要在用c ++代码调用库之前调用init NOT,因为由于时序问题,它可能会崩溃。
我把init调用放到了映射JNI调用的java类构造函数中,这很好用。
//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);
}
}