Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Example of using Object-Oriented Java Native Interface in C++

0.00/5 (No votes)
3 Feb 2006 1  
Demonstrates an easy way of creating and embedding Java objects into C++ code, with Object-Oriented JNI.

Introduction

As I am a lazy boy in writing code, I try to develop tools for reducing my efforts in programming. This article is a result of using the last tool developed by me.

Background

Java Native Interface (JNI) is a set of low level C functions that are very complex to use. A great number of things should be written to support JNI code integrity. Using pure JNI looks like developing OLE modules in MS Visual C++ without using ATL tools and libraries. I have found dozens of products that partially solve this problem, but they all have some serious defects, like:

  • complexity, user should spend a lot of time learning them;
  • unfriendly to use, after generating code, user has to manually modify the project for compiling and execution;
  • huge overhead, many standard modules should be included in the user's code;
  • commercial restrictions, the products are very expensive and assume royalties;
  • huge size of the generated code.

By developing the Object-Oriented JNI (OOJNI) SDK, I tried to solve the problems mentioned above. OOJNI completely hides the low level JNI specifics, making C++ programming with JNI simple and comfortable. This SDK preserves the Java style programming in C++.

Object-Oriented JNI demo project

Before you can compile and run the sample project, please make sure that you have properly installed the Java Development Kit (JDK) version 1.3.x or higher on your computer.

The code of this application was generated with MS Visual Studio and OOJNI Advanced Add-in for VC 6.0. Java class wrappers were created using the runtime library (rt.jar) of IBM JDK 1.3. To run this code, put the EXE and the OOJNI runtime (OOJNI.DLL) modules in the same directory.

The code

1. Creating the EmbeddedAWTFrame demo

The application code was generated with the MFC Application Wizard. EmbeddedAWTFrame Demo starts a dialog as the main window.

2. Generating C++ code for gluing Java objects to the demo

To use the Java objects from C++ code, I have generated C++ JNI wrappers with OOJNI Advanced Add-in for VC 6.0. Each C++ JNI class wraps specific Java class members that are used in my code (this option is supported by the tool):

Java class used C++ wrapper generated Members wrapped
java.awt.Button CPP_Java_Bridge::java:: awt::ButtonJNIProxy constructor:
java.awt.Button();
    method:
void setLabel (java.lang.String s);
java.awt.Color CPP_Java_Bridge::java:: awt::ColorJNIProxy field:
java.awt.Color blue;
java.awt.Panel CPP_Java_Bridge::java:: awt::PanelJNIProxy constuctor:
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 constructor:
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();

Each C++ wrapper does all the routine job hidden from the programmer. Look at the wrapper for the method setLabel from CPP_Java_Bridge::java::awt::ButtonJNIProxy:

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"); 
    } 
}

This code gets the method ID and calls setLabel. It also makes all the C++ - to - Java conversions, and catches Java and C++ exceptions.

In the demo code, I also use a callback interface java.lang.Runnable (it has only one method void run()). For this interface, I generated its C++ implementation (with the tool above):

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); 
}} 
}}}

The method void run() is virtual, empty, and should be overloaded for use:

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. Put it all together

The application starts a dialog as the main window, with Java components embedded in it. To compile the code, I added this line to the EmbeddedAWTFrameDlg.h file:

#include "defproxies.h"

It includes all the referenced OOJNI headers. For activating the Java engine in the CEmbeddedAWTFrameApp::InitInstance() function, I call jni_helpers::JVM::load(). This method loads the default JVM installed on your computer:

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; 
}

The dialog's constructor creates the Java GUI objects, and constructs the Runnable object with the GUI objects created:

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); 
}

On the dialog's WM_SHOWWINDOW event, the Java GUI objects are embedded to the dialog. For demonstrating the usage of the Java callback interface in C++, I do this embedding asynchronously (by calling javax.swing.SwingUtilities.invokeLater with the run object):

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); 
    } 
}

The JVM calls the Runnable::run() function from EventQueue, and here I do the main things:

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();
}

This function gets the WEmbeddedFrame object's window handle to resize it, fills the Panel object with two push buttons, sets its background color to blue, and inserts the Panel to the WEmbeddedFrame object, which is shown.

Embedding SWING components

You can modify the example to embed SWING components into JNI code. But because of a bug in WEmbeddedFrame class SWING components will not respond to key/mouse events. Having been created WEmbeddedFrame object has no LightweightDispatcher associated with it. This dispatcher should redirect events to lightweight components inside the heavyweight container. The dispatcher should be created explicitly in JNI code just after constructing WEmbeddedFrame object. Do this in the function 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; 
}

The wrappers java::awt::ContainerJNIProxy and java::awt::LightweightDispatcherJNIProxy are generated with OOJNI Tool for classes java.awt.Container and java.awt.LightweightDispatcher.

Other resources

  • IBM, SUN JDK1.3.x and higher
  • OOJNI Advanced Add-in Demo for VC 6.0 is available here.

Thanks

I would like to express my deep gratitude to my friend Igor Ladnik for giving me remarks of material significance.

History

  • 18/01/2006 - Initial version.
  • 31/01/2006 - Updated.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here