Java 程序中的 C# 方法调用






3.57/5 (25投票s)
通过 JNI 在 Java 中使用 C# 变得更容易理解了。
引言
您是否曾经(由于某种原因)需要在 Java 程序中使用 C#?是否曾经想过如何在 Java 中调用 C#?好吧,我将尝试解释这个过程需要什么。
背景
我正在处理一些键盘钩取问题,并且发现使用 C# 开发它非常容易。问题在于我最初试图为基于 Java 的程序开发解决方案。经过大量 Internet 搜索后,我终于偶然发现了一篇非常好的文章,开始将这些部分拼凑在一起。由 Judith Bishop、R. Nigel Horspool 和 Basil Worrall 撰写的文章“整合 Java 与 C# 和 .NET 的经验”是我启动这个项目所需的见解。
该文章确实展示了一种在 Java 程序中使用 C# 的方法;但是,我发现很难理解整个过程。在我实际让 C# 在 Java 程序中运行之后,我意识到这个过程可以成为 CodeProject 的一篇文章(我的第一篇文章)。
Using the Code
这张图取自 Judith Bishop、R. Nigel Horspool 和 Basil Worrall 撰写的文章“整合 Java 与 C# 和 .NET 的经验”。
我想使用此代码的最佳方法是进行实验,并将其用作项目的模板。显然,我没有做任何非常复杂的事情,只是提出了一个“Hello World”示例,但我希望人们首先看到最简单的方法。示例代码只是一个基于控制台的 Java 程序,它将显示“Hello World,来自 C#”消息。我还包括所有四种语言的源代码和项目。
您可能会问,为什么需要这么多包装器。好吧,需要几层包装,因为通过将 C# 程序编译成库生成的 DLL 与 Java 的 JNI 不兼容。上面需要两层 C++,因为 JNI 子系统与之交互的 DLL 必须是静态的、程序代码(不是面向对象的)。因此,第一层本质上是 C 代码。幸运的是,静态 C 代码将容纳(指向)C++ 对象的静态指针。静态 C 代码是否可以直接与 C# 对象交互,这值得怀疑,因为需要考虑垃圾回收等问题。
Java 部分
当时我使用 Netbeans 4.0 作为我的主要 Java IDE,因此使用原生代码的过程有点复杂。原因在于 Netbeans 项目希望将代码放在包中,而不是仅使用默认的命名空间。然而,javah 控制台程序并不查看包,因此无法在正确的命名空间中正确创建头文件。因此,正如我发现的那样,有一个简单的两秒钟修复,只需编辑生成的头文件,即可通过 JNI 正确连接到 C++ 中的原生方法。
这是唯一的 Java 代码,它位于 helloworld
包中
public class HelloWorld {
public native void displayHelloWorld();
static {
System.loadLibrary("HelloWorld");
}
public static void main (String[] args) {
new HelloWorld().displayHelloWorld();
}
}
然后,要将其变成 C++ 库所需的头文件,需要运行此命令
javah -jni helloworld.java
该步骤生成以下头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorlds
#define _Included_HelloWorld
#ifdef _cplusplus
extern "C" {
#endif
/*
* Class: HelloWorld
* Method: displayHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
但是,如果我们不更改生成的头文件,那么 JNI 调用将失败,因为由于 NetBeans 和包,该方法具有不同的签名。因此,JNIEXPORT 行更改为
JNIEXPORT void JNICALL Java_helloworld_HelloWorld_displayHelloWorld (JNIEnv *, jobject);
C++ 库
C++ 库的目的是成为调用(通过托管 C++ 包装器)C# 端点代码的 JNI 包装器。
#include <jni.h>
// This is the java header created using the javah -jni command.
#include "Java\HelloWorld.h"
// This is the Managed C++ header that contains the call to the C#
#include "MCPP\HelloWorld.h"
// This is the JNI call to the Managed C++ Class
// NOTE: When the java header was created, the package name was not included
// in the JNI call. This naming convention was corrected by adding the
// "helloworld" name following the following syntax:
// Java_<package name>_<class name>_<method name>
JNIEXPORT void JNICALL
Java_helloworld_HelloWorld_displayHelloWorld(JNIEnv *jn, jobject jobj) {
// Instantiate the MC++ class.
HelloWorldC* t = new HelloWorldC();
// The actual call is made.
t->callCSharpHelloWorld();
}
托管 C++ 库
#using <mscorlib.dll>
#using "CSharpHelloWorld.netmodule"
using namespace System;
public __gc class HelloWorldC
{
public:
// Provide .NET interop and garbage collecting to the pointer.
CSharpHelloWorld __gc *t;
HelloWorldC() {
t = new CSharpHelloWorld();
// Assign the reference a new instance of the object
}
// This inline function is called from the C++ Code
void callCSharpHelloWorld() {
t->displayHelloWorld();
}
};
C# 库
Managed C++ 和 C# 交互所需的步骤是,将 CSharpHelloWorld 库编译为 .netmodule。为此,必须从控制台或作为 C# 项目中的生成后事件运行以下命令(我通过使用生成后进行了简化)
csc /debug /t:module "$(OutDir)\$(ProjectName).dll"
此项目中几乎没有任何代码,因为我只想展示最简单的示例。而我的朋友们,还有什么比“HelloWorld”更简单的呢?
using System;
public class CSharpHelloWorld
{
public CSharpHelloWorld() {}
/// <summary>
/// displayHelloWorld will be the method called from within java.
/// </summary>
public void displayHelloWorld()
{
Console.WriteLine("Hello World, from C#!");
}
}
关注点
使用 Java JNI 确实很痛苦,尤其是在使用 Netbeans 作为 IDE 的情况下。希望我已经为您提供了一些关于此过程的不同步骤的期望的见解。我一定在这个项目中遇到了每一个障碍,并花时间查找了所需的内容。
历史
- 版本 1.0 - 首次发布!