Introduction

Sometimes your Java applications have to talk to each other, and sometimes your Java applications have to communicate with your C++ programs.

Consider the following scenario:

You have a Java program, let's call it JServer. When some interesting events happen, JServer will notify the clients. The clients can be other Java programs or C++ programs. This may look like this:

The JServer has a text area. After the user typed something, the JServer will tell the Java and C++ clients that the data is ready, please get it.

There are many ways to solve these kinds of problems. Here I would like to use Memory Mapped Files and JNI to do the job. Why Memory Mapped Files, because it is efficient. Why JNI, because I do not want to restrict my code to Microsoft Java Virtual Machine.

Warning: Whenever you use JNI, your code is not 100% pure Java anymore. But that's fine with me. :)

Bridge: Named Memory Mapped Files

You can use window messages like WM_COPYDATA, pipe, sockets (just name a few) to share data between different processes on the same machine. But the most efficient way is to use memory mapped files. Because all the mechanisms mentioned above are using the memory mapped files internally, to do the dirty work. The only "problem" with memory mapped files is that all the involving processes must use exactly the same name for the file mapping kernel object. But that is fine with me too. :)

Implementing Memory Mapped Files with Java

It is good that JDK 1.4 provides some memory mapping facilities, but that's not enough. Here I will introduce a very simple Java class MemMapFile. You can easily extend it to do much more complicated work. It has the following fields and native methods:

These sound familiar to you. Yes, MemMapFile is nothing but a Java wrapper to the Win32 APIs about Memory Mapped Files. Also you will notice that I do not have the corresponding LPSECURITY_ATTRIBUTES parameter, that is because I want to make things simple, or I have to write another wrapper class. In the native side, I will just pass NULL (which is acceptable for most of the cases) for this parameter like the following:

When you get the handle of the file mapping object, you need to cast it to jint type and cached in your Java side. When you need it later, you can cast it back to HANDLE. The following is the implementation for the mapViewOfFile, you will see that I am using the HANDLE hMapFile returned from createFileMapping.

The pView pointer is a flat pointer, you can do whatever you want at that address. My writeToMem() and readFromMem() will simply write some string into that memory and read the string from the memory.

When something interesting happens, you will notice your clients. You have many ways to do that. I am lazy, I just put a broadcast method into the MemMapFile, it will broadcast a message telling that the data is ready.

UWM_DATA_READY is a user defined message. User defined message is frequently used to cooperate different processes. If you are not familiar with it, you can go to Dr. Newcomer's homepage, he has an excellent article about Windows message management.

Before you can use user defined message, you have to register it first. Your native implementation DLL entry point function is a good place to register your own message. So you will have something like:

All right, with MemMapFile, now we can share memory among processes, provided these processes know the File-Mapping kernel object name.

Building JServer

It is trivial to make a C++ server that creates the shared memory. It is more interesting to make a Java server. Let's still call it JServer. In your JServer, you need someway similar to the following, to cache the raw pointers return from the native code.

When you want to notify your clients, you can post your just registered message. In my example, after I type something in the text area, I will click the button "Write and Broadcast". The button behavior is the following:

The content in the text area will be written to the shared memory and the data ready message is posted.

When you want to close the server, do not forget calls to unmapViewOfFile() and CloseHandle() to release the resource and unmap the shared memory from your process's address space.

Building Java client

Building a Java client is not very straight forward. The problem is that how the client gets the message that the data ready. Still there are many ways to do it. In this example, I will use a hidden window and JNI callback to deal with the message.

Step 1: Define an interface MemMapFileObserver.

I will explain why I am using an interface in Step 2.

publicinterface MemMapFileObserver {
publicvoid onDataReady();
}

Step 2: Define a proxy MemMapProxy.

MemMapProxy will process the data ready message. Of course I am using JNI again.

Now you must know why I am using an Observer interface. I just want to make the code more generic. Any client that is interested in the data ready message can just implement this interface. In my proxy class, I just have one observer. If you like, you can use an EventListenerList.

The MemMapProxy class looks very simple, while the tricky part is hidden in the init() native method.

As you have seen the init() will start a "daemon" thread. This thread will create a hidden window and initialize some global variables, then it will enter the message loop. The daemon thread will be alive until it gets the WM_QUIT message. Why am I doing this way? The reason is that I need something that can be always running to monitor the window's message after the init() returns.

Before creating the hidden window, I need to do two things. First I have to register a window class. In my example this class is named "dummy window". Second, I have to register the same UWM_DATA_READY message as the one in the MemMapFile. I register my window class and message in my DLL entry point function just like what I did before.

When it gets UWM_DATA_READY message, it will call my Callback(). Callback() is a callback function, it will call fireDataReadyEvent() in my Java side. fireDataReadyEvent() will in turn call the observer's method onDataReady().

JNI callback is very similar to the IDispatch in COM. You have to get the method ID first, based on the method name, then you can invoke the method. However, things will get more interesting if you have multithreads involved. First, JNIEnv* pointer is only valid in the current thread. Second you can not pass local reference to another thread. Our callback happens in our daemon thread, so we have to find a way to get the JNIEnv* pointer and get the reference to our proxy object.

You must have noticed that I have several global variables in my DLL, g_jobj, g_pEnv and g_pJvm. In my init(), I initialized g_jobj by creating a new global reference to the proxy object. Then I can pass it to my daemon thread. In my CreateWndThread(), I can get a pointer to the JVM by JNI_GetCreatedJavaVMs(), then by attaching my daemon thread to the JVM, I can get my JNIEnv* pointer. All right, so far so good. :)

Step 3: Creating a Java client

It is simple to create a Java client. What I need to do is to implement MemMapFileObserver interface.

Building C++ client

Building a C++ is simple. In my example, I am using a MFC Dialog. It has a CEdit control. It will register the UWM_DATA_READY message too. When I get this message, I will read the shared memory and copy the content to my edit control. The following is my onDataReady():

One thing I want to explain is that you need to replace the new line character('\n') with a return character ('\r') followed by a new line character to make the content compatible.

Memory synchronization

There are no major synchronization issues with my simple example. However it will be your big concern if your case is more complicated, or you will be in trouble.

You can not use critical section only to protect your resource, because critical section can only synchronize the threads contained within the same process. However it is not that hard to use Mutex and Semaphore kernel objects to guard your data. Or you can use critical section and kernel objects together if you are more concerned about efficiency. It will be more interesting if you add more native functions to the MemMapFile, like the Wait functions and CreateMutex()...that wrapper the corresponding Win32API. This is a good excise for you. :)

Conclusion

In this article, I am using a simple example to illustrate how to communicate between Java and Java, Java and C++ programs by using shared memory and JNI. I did not use Microsoft Visual J++ and com.ms.xxx.xxxx stuff because I want to use Sun JVM. You can easily extend my example and apply to more complicated cases.

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.

Share

About the Author

Stanley Wang is a Software Development Engineer at Amazon.com. Before he joined Amazon, he was a Lead Software Developer at Vcom3D, Inc. He is the author of JTL (An open sourced JNI C++ template library, http://sourceforge.net/projects/jtl/).

Stanley received his Master's Degree in Computer Science from University of Florida. He is interested in System Programming, Generic Programming, Database Systems and Computer Graphics.