Writing an Eclipse plug-in that uses native code via JNI

Apologies for the non-techy readers of this blog. I’ve got to post this techie stuff somewhere - I’ll eventually probably create a library of technical articles somewhere on this website.

Eclipse’s modularity is successful partly because it contains great tools for creating Eclipse plug-ins. But those tools break down when you want to create a plug-in that includes native code. Of course, you want to avoid that in the first place since it won’t be cross-platform… but sometimes you have to.

Here’s a recipe for creating a native plug-in on Eclipse 3.2. There might be better ways!

First of all, reference sources…

First, if you’re not familiar with JNI, read the introduction in [1] - it’s much more thorough than this. This article just talks about how to do it in Eclipse.

Step One - create your plug-in project

Create a standard Eclipse Java plug-in project.

Creating new Eclipse project
More project creation

I used the “Hello, world” template to create a plug-in which will create a menu command. By default, it prints a string: “Hello, we’ll soon be replaced!”… our intention is to replace that with a string generated by native code.

Check that your plug-in builds and runs (Run As Eclipse Application), and that the menu option appears as intended and does what you wanted.

The dialog before changes

Step Two - making Java code changes to call a native API

Here’s where you write your JNI code in the Java. Refer to [1] if you don’t know enough about JNI. I’m adding:

    public native String getNativeMessage(); 

    static {
        System.loadLibrary("testjniplugin");
    }

and of course changing run to

	public void run(IAction action) {
		String message;
		try {
			message = getNativeMessage();
		} catch (Throwable e) {
			message = e.toString();
		}
		MessageDialog.openInformation(
			window.getShell(),
			"MyProjectWithJNI Plug-in",
			message);
	}

Step Three - watch it break

Now try running the Eclipse application. Impressively, Eclipse works fine, and even gives you a nice error message if you choose the offending action.

Step Three - Add C Nature to the Project

Eclipse projects have one or more “natures” - for example, Java or C++. We want to add a C nature to the project. See [4] and [5] for information about how this should work in future, but for now, it’s a little fiddly.

  1. Choose Window -> Open Perspective -> Other
  2. Select C/C++
  3. Right-click on the project folder
  4. Under New, select Convert to a C/C++ Make Project
  5. Choose C

Adding C nature

If you choose Build Project, you’ll find that nothing much happens. You’ll get an error message:

make -k all
make: *** No rule to make target `all'.

So we have to tinker with the build files.

Step Four - add a Makefile with javah

  1. In the root of the project, select New -> File.
  2. Entitle it Makefile.
  3. In the Makefile, add the following lines:
  4. all : myprojectwithjni_actions_SampleAction.h
    myprojectwithjni_actions_SampleAction.h : bin/myprojectwithjni/actions/SampleAction.class
    	javah -classpath bin -jni myprojectwithjni.actions.SampleAction
    clean :
    	-del myprojectwithjni_actions_SampleAction.h
    

For those new to JNI, javah is a command which takes a Java .class file and creates a C .h file representing the different native functions therein which need to be implemented.

Now right-click on the project and select Build Project. You’ll find that a new file appears in the project view: myprojectwithjni_actions_SampleAction.h. The important line of that file is the function you must implement in your DLL:

JNIEXPORT jstring JNICALL Java_myprojectwithjni_actions_SampleAction_getNativeMessage
  (JNIEnv *, jobject);

Note that we’re being passed a JNIEnv and a jobject - the latter being the SampleAction class on which our method is being called - and returning a jstring.

Step Five - Add a C (or C++) file that implements it

We now add our C code to the project. First choose New -> Source folder, and name it nativesrc. Secondly, add a new source file - SampleAction.c - within that folder. Put in:

#include 
#include “myprojectwithjni_actions_SampleAction.h”
#include  

JNIEXPORT jstring JNICALL Java_myprojectwithjni_actions_SampleAction_getNativeMessage
  (JNIEnv * env, jobject obj)
{
    return (*env)->NewStringUTF(env, “Here’s a native string in UTF8!”);
}

and of course the Makefile will need some changes:

all : testjniplugin.jnilib

testjniplugin.jnilib : nativesrc/SampleAction.c myprojectwithjni_actions_SampleAction.h
	gcc -I . -I /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/headers -c 
		-o testjniplugin.o nativesrc/SampleAction.c
	libtool -dynamic -o libtestjniplugin.jnilib testjniplugin.o

myprojectwithjni_actions_SampleAction.h : bin/myprojectwithjni/actions/SampleAction.class
	javah -classpath bin -jni myprojectwithjni.actions.SampleAction 

clean :
	-del myprojectwithjni_actions_SampleAction.h
	-del testjniplugin.jnilib

(Note the eventual library is getting called libtestjniplugin.jnilib. That’s under MacOS X. The filenames and commands will be somewhat different under Windows. See [1] for the details).

You should see the .jnilib DLL appear in the project:

Whole project

Step Six - Run

That should be all you need. Now run the project and try the menu item in the spawned Eclipse. You should see it working!

Working

If it still doesn’t work, you can try to manually add the DLL to the library search path like this.

Remaining unknowns

  • Whether there’s a way to use Managed make, or Ant buildfiles, to build the native stuff. [3] suggests so, with some hackery. And whether there’s any way to make things cross-platform by doing that.
  • Whether you can arrange for C++ code to hit breakpoints while you’re running your DLL in a spawned Eclipse. Doug at [6] suggests not, for now.

6 Responses to “Writing an Eclipse plug-in that uses native code via JNI”

  1. Adrian Taylor Says:

    And here’s an equivalent makefile that works under Carbide.

    all : testjniplugin.dll
    
    testjniplugin.dll : nativesrc/SampleAction.c exampleJNI_actions_SampleAction.h
    	mwccsym2 -I. -o SampleAction.obj nativesrc/SampleAction.c \
    		-I- "-Ic:\Program Files\Java\jdk1.5.0_09include" \
    		"-Ic:Program FilesJavajdk1.5.0_09includewin32" \
    		-msgstyle parseable -c
    	mwldsym2 -shared -o testjniplugin.dll SampleAction.obj
    
    exampleJNI_actions_SampleAction.h : bin/exampleJNI/actions/SampleAction.class
    	"c:\Program Files\Java\jdk1.5.0_09\bin\javah" -classpath bin -jni exampleJNI.actions.SampleAction 
    
    clean :
    	-del examplejni_actions_SampleAction.h
    	-del testjniplugin.dll
    
  2. www.macrobug.com» Blog Archive » Debugging JNI Eclipse plug-ins Says:

    […] Previously I explained how to create a JNI plug-in for Eclipse, from Eclipse. […]

  3. www.macrobug.com» Blog Archive » Linking to Windows API DLLs from mingw Says:

    […] I thought I had it all cracked when I managed to link to one of my DLLs from within an Eclipse plug-in. Today I wanted to do the same thing, with the only difference being that the DLL is a Microsoft one. […]

  4. Kristina Says:

    I created a Java project in Eclipse referencing a DLL and everything ran properly. In Interface.java I declared the native functions, ie:
    public native int ced_open_file(String filename);
    ….

    static {
    System.loadLibrary(”libparser.dll”);
    }

    Test.java contains the main program.

    I moved the same files in a Plug-in project. I added libparser.dll to the root and added it to build.properties. I get the error:

    java.lang.UnsatisfiedLinkError: myplugin/Interface.open_file
    at myplugin.Interface.open_file(Ljava.lang.String;)I(Interface.java:???)
    at myplugin.Test.main(Test.java:29)

    I have also changed System.load to System.loadLibrary(exact path to dll) and added the path to the VM arguments using -Djava.library.path. I get the same error.

    Have any suggestions to why it works as a Java Project but not as a plug-in?

  5. Adrian Taylor Says:

    Hi, no specific answers. But it looks like it’s failing when it’s trying to call the DLL function rather than when it’s loading the DLL. Maybe you changed the package name or something else which affects the name of the symbol it needs to call?
    Also from your code example, it is trying to call Interface.open_file but your native method is ced_open_file

  6. Kristina Says:

    Yes, when I debug I fail when trying to call the DLL function not when loading. Someone suggested adding…
    Bundle-NativeCode: libparser.dll;osname=win32;processor=x86
    to the header of MANIFEST.MF. However, it didn’t make any difference when I ran my code. Have you used Bundle-NativeCode?

    And yeah the code example difference was a typo.

Leave a Reply