WonDLLand revisited

I previously documented my problems getting my Java/JNI wrapper for OziAPI to work. As I said in my previous post, some of my problems were down to the OziAPI DLL not getting a chance to clean up properly when the application exited. Over the Xmas break I continued hacking away and wrapping more of the interface, but when I got to the OziExplorer callback hooks I hit another showstopper. The hooks allow you to trap mouse clicks on the map displayed in OziExplorer, but every time I activated one of the callback hooks and then clicked on a map the application hung, and it stayed hung until I quit the Java application. My initial thought was that I was falling afoul of the awful mess of calling convention types on Win32 and that something was barfing on a malformed stack frame somewhere on the round trip from my Java app to OziExplorer and back. I was particularly suspicious because I was using the MinGW tools to build the DLL containing my JNI code rather than the Microsoft tools.

To try to track down the root cause I hacked together a console-only C program and DLL to mimic my Java/JNI setup, and whilst that worked fine with the non-callback OziAPI functions (as did the Java/JNI version), any callback functions hung in just the same way as the Java/JNI version. I noticed that the Visual C++ example included with the OziAPI DLL contained a Windows GetMessage loop, so I added one to my toy program and hey presto, it worked! This implied that OziAPI uses windows messages to implement the callback mechanism, and an email to Des Newman, the OziExplorer developer, confirmed that was in fact the case.

That didn't really help explain what was wrong with the Java/JNI setup - the Java code was a Swing application, so it had to contain a Windows dispatch loop anyway - right? What on earth was going on? I was more more less ready to admit defeat when I stumbled across an old JavaWorld article that contained the following:

Messages are not sent directly to windows, but put on an event queue owned by a thread. The thread must occasionally check for messages on its queue, and choose to either deal with them or delegate them to other callbacks ... we cannot be sure that these threads (which are owned by the VM process) check their Win32 message queues and delegate messages. In this case, in fact, they don't. Because of this, we will create our own thread, with our own message queue handling, and make sure all our windows are created within its context. It is simple to asynchronously delegate work to our worker thread by posting custom messages to its message queue.

Ahah! So it appeared that it is necessary to have a custom dispatch loop to handle the Windows messages dispatched by the OziAPI callback hooks. However, because this thread has in turn got to be able to dispatch the callbacks to Java methods, we have to play nicely with the JVM as well. We also have to make sure that the events from the OziAPI DLL get dispatched to our dispatch thread - some experimentation revealed that OziAPI sends them to the first thread that makes a callback-related OziAPI call. Putting this all together, here's what is necessary to get it to work:

  • Create a native Windows thread.
  • Call PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE) to force creation of a new Windows message queue.
  • Make a dummmy oziMapSingleClickOFF() call so OziAPI routes the callbacks to the new thread.
  • Attach the new thread to the JVM with a call to AttachCurrentThread()
  • Enter a message dispatch loop to handle the callback events.

The various OziAPI calls necessary to enable and disable the callback hooks are also made from inside the new thread - the event loop responds to custom WM_APP messages and enables or disables the callbacks as required. The callbacks themselves are handled by C routines that vector the events to the appropriate Java method calls. I've left out all details of the necessary inter-thread synchronisation that's needed, but that's an overview of how it all hangs together.

I'm still working on wrapping the rest of the API - it's rather large so it is going to take a while, but once I have it knocked into shape I'm intending to release the code here on SourceForge.

Categories : Java, Tech