Alan Burlison's Work Related Ramblings

All | General | Java | NetBeans | Perl | Solaris
« Using NetBeans with... | Main | Netbeans, JNI, Webst... »

20060627 Tuesday June 27, 2006

Building Win32 JNI code using NetBeans and MinGW

This entry is copied from my personal blog - I'm going to revamp that site in a while, so I've put a copy of it over here with the rest of my Java and NetBeans-related stuff. I've updated it to refer to the latest versions of both the JDK and NetBeans.

I use a GPS mapping application called OziExplorer, and just for laughs I started fiddling around with encapsulating the API for the application so that I could use it from Java. Access to the API is via a DLL, which meant delving into the scary world of both JNI and building DLLs on Win32. Whenever I've tried to do this in the past, it always seemed to require loading vast amounts of unwanted crap on my machine so that I got a compiler that worked, arcane spells involving mapfiles, sacrifical slaughter of various wildlife and so much pain in general that I'd always given up in disgust. However, I'm a glutton for punishment so I thought I'd have another crack at it. I couldn't find anything on the web that told you everything you needed to know all in one place, so I thought I'd document it since I'd got it all working.

Step 1

If you don't already have it, download and install the latest version of the Java Development Kit. I used version 1.5.0_07, the instructions below should work with other versions... Do this first to make you life easy - the NetBeans installer will pick up the latest version of the JDK that you have installed.

Step 2

If you don't already have it, download Netbeans and install it. I used version 5.0, that or anything later should be fine.

Step 3

Install the Ant cpptasks module. This adds support to Ant for building C code. The only bit of this you need is cpptasks.jar, and you should copy it to the lib subdirectory of the new version of Ant you installed in step 3.

Step 4

Install a copy of MinGW. This is a Win32 version of the well-know gcc compiler. Unfortunately the MinGW documentation is as clear as mud on exactly how to do this - if you go to the download page you get a huge shopping list of bits, with no clear directions as to which bits you actually need - the documentation is clearly written with the assumption that you already know. The easiest way to sort the mess out is to download the "MinGW" installer - look for the section entitled "MinGW" and you'll see a file called MinGW-<version>.exe, in my case <version> was 4.1.0. Download and run that and it will ask you for an installation location (I used C:\Program Files\MinGW, then deselect everything except The minimal set of packages required to build C/C++. Leave everything on the next screen selected and start the install. The installer will then pull the bits you need from the web. You can probably prune the list, but the whole thing is only 60Mb so it didn't seem worth the effort.

Step 5

Set up your environment. In order for Ant to be able to run the compiler, it will need to be able to find it, and the same goes for any DLLs that you will use or generate. Windows particularly sucks in this area, so to save spraying DLLs all over my C drive I created the directory C:\DLLs to dump everything in. With that done, right click on My Computer, click on Advanced then Environment Variables. Select Path from the list, then add ;C:\Program Files\MinGW\bin;c:\DLLs to the end, or wherever you installed the compiler, and wherever you intend to install your DLLs. If it isn't already there, add the path to the Java bin directory as well - in my case this is ;C:\Program Files\Java\jdk1.5.0_04\bin. I don't know if it is necessary, but at this point I logged out/into Windows to make sure my environment changes took effect.

Step 6

Add the appropriate JNI code to your project - I don't intend to cover the process for doing that here, instead see the JNI documentation. The tricky bits are getting everything to link together, the problem being caused by the awful Win32 name decoration mess. Basically, DLLs usually use the stdcall calling convention, and when you build them with gcc by default it 'decorates' the symbol names by appending '@' and a number (the number of bytes that will be popped off the stack by the function) - follow the links for details. There are two main issues:

Step 7

Javap can be used to output the method and field signatures that will be needed in the JNI C code. Unfortunately there isn't an Ant target to do this, so it's necessary to roll our own. The first thing is to put the following 2-line Windows batch script somewhere - I called mine javap-s.bat and put it under the nbproject subdirectory of my NetBeans project. See the following section for the explanation of how to hook it into Ant.

@echo off
javap -s -private -classpath %~dps1 %~n1 > %~s2

Step 8

The next tricky bit is adding the necessary goop to your project.xml file to get Ant to build your code bearing in mind the issues I described above. This isn't helped by the fact that the cpptasks add-on has a bug that means it doesn't work properly with MinGW. Here's what works for me. Firstly, add the following to the nbproject/project.properties file:

lib.dir=lib
jni.dir=jni
signatures.dir=signatures

Then add the following to the build.xml file:

    <!-- Load the cpptasks task -->
    <taskdef resource="cpptasks.tasks"/>
    <typedef resource="cpptasks.types"/>

    <!-- Compile the JNI code into a DLL -->
    <target name="-post-compile">
        <!-- Make sure the output directories exists -->
        <mkdir dir="${jni.dir}"/>
        <mkdir dir="${signatures.dir}"/>

        <!-- Run javah to produce a header file for the JNI functions -->
        <javah verbose="yes" classpath="${build.classes.dir}"
         destdir="${jni.dir}">
            <class name="com.oziexplorer.OziAPI"/>
        </javah>

        <!-- Run javap to produce files containing the JNI signatures -->
        <apply executable="nbproject/javap-s.bat" dest="${signatures.dir}"
          resolveexecutable="true" failonerror="true" ignoremissing="false">
            <fileset dir="${build.classes.dir}/com/oziexplorer"
              includes="*.class"/>
            <mapper type="glob" from="*.class" to="*.txt"/>
            <srcfile/>
            <targetfile/>
        </apply>

        <!-- Check the library definition file is up to date -->
        <apply executable="dlltool" dest="${jni.dir}" failonerror="true"
          ignoremissing="false">
            <filelist dir="${src.dir}/com/oziexplorer" files="OziAPI.def"/>
            <mapper type="glob" from="*.def" to="lib*.a"/>
            <arg value="-k"/>
            <arg value="--dllname"/>
            <arg value="OziAPI.dll"/>
            <arg value="--def"/>
            <srcfile/>
            <arg value="--output-lib"/>
            <targetfile/>
        </apply>

        <!-- Compile the C code -->
        <cc link="shared" outtype="shared" multithreaded="true" optimize="speed"
          objdir="${jni.dir}" outfile="${jni.dir}/OziAPI">
            <compilerarg value="-Wall"/>
            <compilerarg value="-D_JNI_IMPLEMENTATION_"/>
            <compilerarg value="-fno-strict-aliasing"/>
            <linker name="gcc">
                <linkerarg value="--kill-at"/>
                <linkerarg value="-oOziAPIJava.dll"/>
            </linker>
            <sysincludepath location="${java.home}/../include"/>
            <sysincludepath location="${java.home}/../include/win32"/>
            <fileset dir="${src.dir}/com/oziexplorer" includes="OziAPI.c"/>
            <libset dir="${jni.dir}" libs="OziAPI"/>
        </cc>
    </target>

    <!-- Copy the stripped DLL and OziAPI.DLL to the dist directory -->
    <target name="-post-jar">
        <apply executable="strip" dest="${dist.dir}" failonerror="true"
          ignoremissing="false">
            <filelist dir="${jni.dir}" files="OziAPIJava.dll"/>
            <mapper type="glob" from="*.dll" to="*.dll"/>
            <arg value="-s"/>
            <srcfile/>
            <arg value="-o"/>
            <targetfile/>
        </apply>
        <copy todir="${dist.dir}">
            <fileset dir="${lib.dir}" includes="*.dll"/>
        </copy>
    </target>

Let's go through it bit at a time:

Although it took a bit of fiddling to get all the bits to play together, the process of building DLLs is far easier than it was in the past - the work that the gcc and MinGW crowd have done to do all the dirty work for you is really very impressive.

Posted by alanbur ( Jun 27 2006, 04:37:33 PM BST ) Permalink Comments [0]

Trackback URL: http://blogs.sun.com/alanbur/entry/building_win32_jni_code_using
Comments:

Post a Comment:

Name:
E-Mail:
URL:

Your Comment:

HTML Syntax: NOT allowed