65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.73/5 (10投票s)

2006年1月24日

CPOL

4分钟阅读

viewsIcon

80160

downloadIcon

1761

演示了一种在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();
    方法:
void setLabel (java.lang.String s);
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>
    method
java.awt.Component add(java.awt.Component c);
    method
void setBackground(java.awt.Color c);
javax.swing. SwingUtilities CPP_Java_Bridge::javax:: swing::SwingUtilitiesJNIProxy method
static void invokeLater(java.lang.Runnable r);
sun.awt.windows. WEmbeddedFrame CPP_Java_Bridge::sun::awt:: windows::WEmbeddedFrameJNIProxy 构造函数
sun.awt.windows. WEmbeddedFrame(int n);
    method
java.awt.Component add(java.awt.Component c);
    method
void setBounds(int x, int y, int w, int h);
    method
void 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 - 更新。
© . All rights reserved.