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();
#ifdef _AFXDLL Enable3dControls();
#else Enable3dControlsStatic();
#endif
jni_helpers::JVM::load();
CEmbeddedAWTFrameDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
} else if (nResponse == IDCANCEL)
{
}
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 )
: CDialog(CEmbeddedAWTFrameDlg::IDD, pParent)
,embFrm((jobject)0)
,run(embFrm, button1, button2, panel)
{
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);
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.