在 C++ 中使用面向对象的 Java Native Interface 的示例






3.73/5 (10投票s)
演示了一种在C++代码中创建和嵌入Java对象(使用面向对象的JNI)的简单方法。
引言
作为一个懒惰的程序员,我总是试图开发工具来减少编程时的工作量。本文就是使用我开发的最后一个工具的结果。
背景
Java Native Interface (JNI) 是一组非常复杂的底层C函数。需要编写大量代码来支持JNI的代码完整性。使用纯JNI就像在MS Visual C++中使用ATL工具和库开发OLE模块一样。我发现了很多部分解决了这个问题,但它们都有一些严重的缺陷,例如:
- 复杂性,用户需要花费大量时间学习它们;
- 使用不便,生成代码后,用户需要手动修改项目才能编译和执行;
- 巨大的开销,用户的代码中需要包含许多标准模块;
- 商业限制,产品非常昂贵并需要支付版税;
- 生成代码的体积庞大。
通过开发面向对象的JNI (OOJNI) SDK,我试图解决上述问题。OOJNI完全隐藏了底层的JNI细节,使JNI的C++编程简单舒适。该SDK保留了C++中Java风格的编程。
面向对象的JNI演示项目
在编译和运行示例项目之前,请确保您的计算机上已正确安装Java Development Kit (JDK) 1.3.x 或更高版本。
此应用程序的代码使用MS Visual Studio和OOJNI Advanced Add-in for VC 6.0生成。Java类包装器是使用IBM JDK 1.3的运行时库(rt.jar)创建的。要运行此代码,请将EXE文件和OOJNI运行时(OOJNI.DLL)模块放在同一目录下。
代码
1. 创建EmbeddedAWTFrame演示
应用程序代码使用MFC Application Wizard生成。EmbeddedAWTFrame Demo以一个对话框作为主窗口。
2. 为连接Java对象到演示生成C++代码
为了在C++代码中使用Java对象,我使用OOJNI Advanced Add-in for VC 6.0生成了C++ JNI包装器。每个C++ JNI类包装了我的代码中使用的特定Java类成员(该选项由工具支持)。
使用的Java类 | 生成的C++包装器 | 包装的成员 |
java.awt.Button |
CPP_Java_Bridge::java:: awt::ButtonJNIProxy |
构造函数java.awt.Button(); |
方法:
| ||
java.awt.Color |
CPP_Java_Bridge::java:: awt::ColorJNIProxy |
字段java.awt.Color blue; |
java.awt.Panel |
CPP_Java_Bridge::java:: awt::PanelJNIProxy |
构造函数java.awt.Panel();<CODE> |
methodjava.awt.Component add(java.awt.Component c); | ||
methodvoid setBackground(java.awt.Color c); | ||
javax.swing. SwingUtilities |
CPP_Java_Bridge::javax:: swing::SwingUtilitiesJNIProxy |
methodstatic void invokeLater(java.lang.Runnable r); |
sun.awt.windows. WEmbeddedFrame |
CPP_Java_Bridge::sun::awt:: windows::WEmbeddedFrameJNIProxy |
构造函数sun.awt.windows. WEmbeddedFrame(int n); |
methodjava.awt.Component add(java.awt.Component c); | ||
methodvoid setBounds(int x, int y, int w, int h); | ||
methodvoid show(); |
每个C++包装器都完成了所有常规工作,并隐藏了程序员。看看`CPP_Java_Bridge::java::awt::ButtonJNIProxy`中`setLabel`方法的包装器。
void CPP_Java_Bridge::java::awt::ButtonJNIProxy:: setLabel(::jni_helpers::JStringHelper p0) { try{ if(setLabel_ID == 0) setLabel_ID = ::jni_helpers::JNIEnvHelper:: GetMethodID(clazz, "setLabel", "(Ljava/lang/String;)V"); ::jni_helpers::JNIEnvHelper::CallVoidMethod(*this, setLabel_ID, (jobject)p0); }catch(::jni_helpers::JVMException e){ throw ::jni_helpers::JVMException(jni_helpers:: CSmartString("CPP_Java_Bridge::java::awt:: ButtonJNIProxy::setLabel:\n") + e.getMessage()); }catch(...){ throw ::jni_helpers::JVMException("CPP_Java_Bridge" "::java::awt::ButtonJNIProxy::" "setLabel: System Error"); } }
这段代码获取方法ID并调用`setLabel`。它还执行所有C++到Java的转换,并捕获Java和C++异常。
在演示代码中,我还使用了一个回调接口`java.lang.Runnable`(它只有一个方法`run()`)。对于这个接口,我用上面的工具生成了它的C++实现。
namespace CPP_Java_Bridge{ namespace java{ namespace lang{ #ifdef __JNIHDLL class __declspec(dllexport) RunnableJNIImpl: public jni_helpers::JNIInterface{ #elif __USEDLL class __declspec(dllimport) RunnableJNIImpl: public jni_helpers::JNIInterface{ #else class RunnableJNIImpl: public jni_helpers::JNIInterface{ #endif static const JNINativeMethod methods[]; static const jbyte bytes[]; static const char* _clazzName; public: RunnableJNIImpl(); virtual void run(){} }; namespace RunnableJNIImpl_Native{ extern "C" { JNIEXPORT void JNICALL run(JNIEnv *, jobject, jlong); }} }}}
方法`run()`是虚函数,为空,应该被重载后使用。
class Runnable: public CPP_Java_Bridge::java::lang::RunnableJNIImpl { CPP_Java_Bridge::sun::awt:: windows::WEmbeddedFrameJNIProxy& frm; CPP_Java_Bridge::java::awt::ButtonJNIProxy& button1; CPP_Java_Bridge::java::awt::ButtonJNIProxy& button2; CPP_Java_Bridge::java::awt::PanelJNIProxy& panel; CRect rc; public: Runnable(CPP_Java_Bridge::sun::awt:: windows::WEmbeddedFrameJNIProxy& embFrm, CPP_Java_Bridge::java::awt::ButtonJNIProxy& b1, CPP_Java_Bridge::java::awt::ButtonJNIProxy& b2, CPP_Java_Bridge::java::awt::PanelJNIProxy& p): CPP_Java_Bridge::java::lang::RunnableJNIImpl() ,frm(embFrm) ,button1(b1) ,button2(b2) ,panel(p) { } void init(HWND hwnd, CRect& rc) { CPP_Java_Bridge::sun::awt::windows:: WEmbeddedFrameJNIProxy tmp((long)hwnd); frm = JObject_t(tmp); this->rc = rc; } void run(); };
3. 将它们组合起来
该应用程序以一个对话框作为主窗口,其中嵌入了Java组件。为了编译代码,我在EmbeddedAWTFrameDlg.h文件中添加了这行:
#include "defproxies.h"
它包含了所有引用的OOJNI头文件。在`CEmbeddedAWTFrameApp::InitInstance()`函数中激活Java引擎,我调用`jni_helpers::JVM::load()`。此方法加载您计算机上安装的默认JVM。
BOOL CEmbeddedAWTFrameApp::InitInstance() { AfxEnableControlContainer(); // Standard initialization // If you are not using these features // and wish to reduce the size // of your final executable, // you should remove from the following // the specific initialization // routines you do not need. #ifdef _AFXDLL Enable3dControls(); // Call this when using MFC in a shared DLL #else Enable3dControlsStatic(); // Call this when linking to MFC statically #endif jni_helpers::JVM::load(); CEmbeddedAWTFrameDlg dlg; m_pMainWnd = &dlg; int nResponse = dlg.DoModal(); if (nResponse == IDOK) { // TODO: Place code here // to handle when the dialog is // dismissed with OK } else if (nResponse == IDCANCEL) { // TODO: Place code here // to handle when the dialog is // dismissed with Cancel } // Since the dialog has been closed, // return FALSE so that we exit the // application, rather than start // the application's message pump. return FALSE; }
对话框的构造函数创建Java GUI对象,并用创建的GUI对象构造`Runnable`对象。
CEmbeddedAWTFrameDlg::CEmbeddedAWTFrameDlg(CWnd* pParent /*=NULL*/) : CDialog(CEmbeddedAWTFrameDlg::IDD, pParent) ,embFrm((jobject)0) ,run(embFrm, button1, button2, panel) { //{{AFX_DATA_INIT(CEmbeddedAWTFrameDlg) // NOTE: the ClassWizard will // add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require // a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
在对话框的`WM_SHOWWINDOW`事件上,Java GUI对象被嵌入到对话框中。为了演示在C++中使用Java回调接口,我以异步方式进行嵌入(通过调用`javax.swing.SwingUtilities.invokeLater`并传入`run`对象)。
void CEmbeddedAWTFrameDlg::OnShowWindow(BOOL bShow, UINT nStatus) { CDialog::OnShowWindow(bShow, nStatus); if(IsWindow(m_hWnd)) { CRect rc; CRect rcDlgRc; CRect rcDlgCl; GetWindowRect(&rcDlgRc); GetClientRect(&rcDlgCl); m_oStatic.GetWindowRect(&rc); ScreenToClient(&rc); run.init(m_hWnd, rc); CPP_Java_Bridge::javax::swing:: SwingUtilitiesJNIProxy::invokeLater(run); } }
JVM从`EventQueue`调用`Runnable::run()`函数,在这里我执行主要操作。
void Runnable::run() { HWND h = (HWND)jni_helpers::JNIEnvHelper:: getNativeFromComponent(frm); ::MoveWindow(h, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE); button1.setLabel("Java Button1"); button2.setLabel("Java Button2"); panel.add(button1); panel.add(button2); panel.setBackground(CPP_Java_Bridge::java:: awt::ColorJNIProxy::get_blue()); frm.add(panel); frm.show(); }
此函数获取`WEmbeddedFrame`对象的窗口句柄来调整它的大小,用两个按钮填充`Panel`对象,将其背景颜色设置为蓝色,并将`Panel`插入到`WEmbeddedFrame`对象中,然后显示该对象。
嵌入SWING组件
您可以修改示例以将SWING组件嵌入到JNI代码中。但是,由于`WEmbeddedFrame`类中的一个bug,SWING组件将无法响应键盘/鼠标事件。创建`WEmbeddedFrame`对象后,它没有关联的`LightweightDispatcher`。这个调度器应该将事件重定向到重型容器内的轻量级组件。调度器应该在JNI代码中,在构造`WEmbeddedFrame`对象后显式创建。在`Runnable::init()`函数中执行此操作。
void init(HWND hwnd, CRect& rc) { CPP_Java_Bridge::sun::awt::windows:: WEmbeddedFrameJNIProxy tmp((long)hwnd); // Create LightweightDispatcher object to enable // lightweight component in WEmbeddedFrame. We use // java::awt::ContainerJNIProxy to access its private // member field 'dispatcher' java::awt::ContainerJNIProxy c(tmp); c.dispatcher = java::awt::LightweightDispatcherJNIProxy((java::awt::Container)tmp); frm = JObject_t(tmp); this->rc = rc; }
`java::awt::ContainerJNIProxy`和`java::awt::LightweightDispatcherJNIProxy`包装器是使用**OOJNI Tool**为`java.awt.Container`和`java.awt.LightweightDispatcher`类生成的。
其他资源
- IBM, SUN JDK1.3.x 及更高版本
- **OOJNI Advanced Add-in Demo for VC 6.0** 可在此 处获取。
谢谢
我非常感谢我的朋友Igor Ladnik给我提供了有重要意义的建议。
历史
- 2006/01/18 - 初始版本。
- 2006/01/31 - 更新。