使用 Netbeans 和 Visual Studio 调试 JNI 应用程序
本文介绍如何使用 Netbeans 创建 Java Swing GUI 应用程序并将其与 JNI DLL 接口。
引言
本文解释了如何使用 Netbeans IDE 创建 Java swing 应用程序,以及如何使用 JNI 与原生 C++ 代码进行通信。它还展示了如何调试 Java 应用程序和原生 C++ DLL。
背景
很多时候,我们可能面临这样的情况:应用程序的主要部分,包括 GUI,需要在 Java 中开发,而一些用 C 或 C++ 编写的遗留库需要用于应用程序的某些功能。在这种情况下,Java Native Interface (JNI) 被用来围绕 C/C++ 库调用编写包装器,然后将这些包装器 DLL 与 Java 代码接口。
开始吧...
首先,让我们开始使用 Netbeans IDE 创建一个简单的桌面应用程序。
在 Netbeans 中启动一个新 Java 项目,并在“新建项目”向导中选择“Java Desktop application”。
在下一页,将项目名称设置为 `NativeGUI`,您可以选择喜欢的名称。
向导将用基本桌面应用程序的代码填充项目,并打开交互式 GUI 编辑器。
删除菜单栏和状态栏。按照下图所示将组件添加到 GUI 中。
通过切换到“源代码”视图模式,删除 `NativeGUIView.java` 文件中的第 29 到 81 行。这是为需要删除的状态栏动画自动生成的代码。还请删除未使用的成员变量。Netbeans IDE 将在视觉上帮助您完成所有这些操作。或者,您可以选择保留菜单栏和状态栏,以避免正确删除代码的麻烦。
切换回“设计”视图模式,右键单击“Add”按钮,然后在上下文菜单中选择“Set Action”。您将看到“Set Action”对话框。在“Action”组合框中下拉并选择“Create New Action”。
将操作名称输入为 `onAddClicked`,然后按 OK。
向导将添加侦听器的代码,然后您将被带到代码编辑器,您将在其中输入以下代码——以粗体显示。
@Action
public void onAddClicked() {
double d = 0.0;
try
{
d = NativeAdd.add(Double.parseDouble(jSpinner1.getValue().toString()),
Double.parseDouble(jSpinner2.getValue().toString()));
}
catch(Exception e)
{
JOptionPane.showMessageDialog(mainPanel, e.getMessage());
}
jTextField1.setText(Double.toString(d));
}
同样,为“Quit”按钮创建操作并进行设置。您可以单击编辑器上方面板中的“Design”按钮切换回 UI 编辑器。
使用相同的过程添加侦听器。
@Action
public void onQuitClicked() {
System.exit(0);
}
现在,在“Projects”视图中右键单击 `nativegui` 包,然后单击“Add”->“Java Class”。在“Class”对话框中,输入类名 `NativeAdd`,然后单击“Finish”。
代码编辑器将打开新添加的类代码。将以下 `static` 方法行添加到类中(以粗体显示)。此类是用于加载和执行原生 DLL 并执行其中方法的包装器类。
public class NativeAdd {
native public static double add(double n1, double n2);
static
{
System.loadLibrary( "NativeAdd" );
}
}
通过单击上面的工具栏中的绿色箭头运行一次项目。
这将为我们服务两个目的:
- 我们可以看到 GUI 的外观,以便进行布局上的必要更改。
- 它将为所有 java 文件生成类文件。我们需要 `NativeAdd.class` 文件才能进行 JNI 的进一步处理。
单击“Add”按钮,您将在 Netbeans 输出窗口中看到大量错误。这些与缺失的 DLL 有关,我们应该提供该 DLL,以便 `System.loadLibrary( "NativeAdd" );` 行能够找到要加载的 DLL。当我们创建带有 JNI 代码的 DLL 并将其放置在 JVM 可定位的文件夹中时,这将得到解决。
现在打开一个命令提示符窗口,导航到 `<Your Project Folder>\NativeGUI\build\classes\nativegui` 文件夹。所有已构建的类都可以在此处找到。**切换到上一级目录**并发出以下命令:
javah nativegui.NativeAdd
`javah.exe` 是 JNI 头文件生成工具,可以在您的 Java SDK 安装的 `bin` 文件夹中找到。确保它在您的路径中。
您将在生成的文件夹中找到一个名为 `nativegui_NativeAdd.h` 的 C 头文件。文件名格式为 `packagename_classname.h`。打开它会发现一个名为 `Java_nativegui_NativeAdd_add` 的函数。名称格式为 `Java_packagename_classname_methodname`。此函数实际上将从名为 `NativeAdd.dll` 的 DLL 中加载。还记得我们为 `System.loadLibrary` 方法提供的参数吗?这就是 `nativegui_NativeAdd.h` 文件的内容。
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class nativegui_NativeAdd */
#ifndef _Included_nativegui_NativeAdd
#define _Included_nativegui_NativeAdd
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: nativegui_NativeAdd
* Method: add
* Signature: (DD)D
*/
JNIEXPORT jdouble JNICALL Java_nativegui_NativeAdd_add
(JNIEnv *, jclass, jdouble, jdouble);
#ifdef __cplusplus
}
#endif
#endif
我们将使用此代码来创建实现该函数的 JNI DLL。请记住,`javah.exe` 只生成了头文件。我们需要提供函数实现。请记住,我们在 `NativeAdd.dll` 中实现的仅是原生/遗留软件 API 的包装器,我们希望与 Java 一起使用。在这里,为演示起见,我们只编写代码来添加两个数字。在实际应用程序中,会调用原生 API 的代码。JNI 类和方法的设计应在仔细研究您希望调用的原生 API 函数或类之后进行。您也可以在 Windows 上调用 COM API。
现在让我们开始实现 DLL 的代码。
启动 Visual Studio,并创建一个名为 `NativeAdd` 的新 Win32 项目。
选择“DLL”作为应用程序类型,然后单击“Finish”。

项目将创建并填充用于创建 DLL 的代码。`NativeAdd.cpp` 文件也已创建,仅包含 `stdafx.h`。向项目中添加一个名为 `NativeAdd.h` 的新头文件。然后将 `nativegui_NativeAdd.h` 文件(在 `netbeans` 文件夹中生成)的内容复制并粘贴到 `Nativeadd.h` 文件中。在 `Nativeadd.cpp` 文件中实现函数,如下所示。
#include "NativeAdd.h"
jdouble JNICALL Java_nativegui_NativeAdd_add
(JNIEnv *env, jclass cls, jdouble n1, jdouble n2)
{
return n1 + n2;
}
通过以下步骤将 Java include 文件夹路径添加到 Visual Studio:
1) 在“Solution Explorer”中,右键单击项目 ->“Properties”。
2) 在“Property Pages”中,展开“C/C++”树并选择“General”节点。
3) 在页面中,将“<path_to_jdk>\include”和“<path_to_jdk>\include\win32”文件夹路径添加到“Additional Include Directories”。
或通过以下步骤:
1) 在菜单栏中单击“Tools”->“Options”。
2) 在“Options”对话框中,导航到“Projects and Solutions”->“VC++ Directories”节点。
3) 在页面中,使用标有“Show Directories For”的下拉框选择“Include Files”。
4) 为“<path_to_jdk>\include”和“<path_to_jdk>\include\win32”文件夹路径创建新条目。
第二种选择比第一种选择更可取,因为第一种选择只针对项目。
现在我们可以构建项目了。
构建项目。`NativeAdd.dll` 文件将在 DLL 项目路径的 `Debug` 文件夹中创建。
注意:如果您需要在 C++ 代码中访问 JVM,您还必须将库路径和 `jvm.lib` 库条目添加到链接器中。
现在我们有了 JNI 代码 DLL 和使用它的 Java GUI。但是 DLL 如何加载呢?为此,您必须将 DLL 复制到 `<Your Java SDK Folder>\bin`。但是,如果我们从 Netbeans 运行 GUI 项目,则不需要这样做。我们可以在“Project Properties”对话框的“Run”设置中设置工作目录。您可以右键单击 Netbeans “Project View”窗口中的项目,然后单击“Properties”来打开对话框。
请确保选择您项目的路径……而不是我的!:)
现在运行 GUI 项目并测试两个数字的加法。万岁!您刚刚使用 JNI 将原生 C 代码接口到了 Java 应用程序。现在让我们进入应用程序的调试,并查看原生 C 代码是如何被调用的。通过单击工具栏上的“Debug”按钮开始调试 Java 程序。在 `NativeGUIView` 类的 `onAddClicked` 方法中设置一个断点。在应用程序的 spinboxes 中输入两个值,然后单击 Add。
调试器在断点处停止。
现在我们必须在原生 DLL 代码中设置断点。在 Visual Studio 中,单击“Debug”->“Attach to Process”。
在“Attach to Process”对话框中,选择运行您应用程序的 `java.exe`。请记住,如果您正在运行其他 Java 应用程序,您的系统上可能会运行多个 `java.exe` 实例。请务必选择正在运行您应用程序的 `java.exe`。单击“Attach”。
然后,在 Visual Studio 中,在原生 JNI 函数实现中添加一个断点。
**注意**:断点将不会被命中,因为 DLL 尚未被 JVM 加载。断点符号会带有一个小的警告图标,并且工具提示会显示它不会被命中。
返回 Netbeans IDE,按 F8 逐步执行每块代码。在您逐步执行调用 `NativeADD.add` 方法的行之后,JVM 将动态加载 DLL,Visual Studio 将自动显示在顶部并命中断点。
通过按 F10 在 Visual Studio 中逐步执行代码,最后在 Visual Studio 显示显示反汇编的警告消息框时按 F5。Java 应用程序将重新显示在顶部。然后,为了进一步调试应用程序,请返回 Netbeans IDE 并继续调试。
希望您喜欢这篇文章!
关注点
COM 服务器应用程序也可以通过将服务器 EXE 附加到调试器以类似的方式进行调试。
提示
这是 CodeProject 会员 **wc2009** 提供的一个附加技巧(请参阅评论部分)。如果您想使用 MinGW 编译器编译 DLL 项目,则必须在附加编译器选项中添加“-Wl,--add-stdcall-alias
”。
历史
- 2010 年 3 月 31 日:首次发布
- 2010 年 4 月 12 日:添加了 MinGW 编译器的提示