Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I avoid UnsatisfiedLinkError when calling C++ from java from C++ application?

I'm embedding Java into a C++ application. As part of this I need to expose native functions to java, as well as calling java functions from C++.

Do I need to put the functions I want to call from java into a shared library? Or can they be compiled into the host application somehow?

Here's what I've tried so far, but it gives a java.lang.UnsatisfiedLinkError

Compilation

I'm building on OS X 10.5 using

g++ -Wall -I/System/Library/Frameworks/JavaVM.framework/Headers/ -framework JavaVM -g test.cpp

Java Test File : TestObject.java

// To build this you need to do a `javac TestObject.java`
// To get the signatures do a `javap -d TestObject`
// To generate the .h file do a `javah TestObject`
public class TestObject
{
    public native TestObject get_property( String k ); 
}

C++ Test File : test.cpp

#include <jni.h>
#include <assert.h>


JNIEXPORT jobject JNICALL Java_TestObject_get_1property(JNIEnv * jni_env, jobject obj, jstring key)
{
  //Just a stub implementation for now.
  jclass klass = jni_env->GetObjectClass( obj );
  jmethodID constructor = jni_env->GetMethodID( klass, "<init>", "()V");
  jobject retval = jni_env->NewObject(klass, constructor );

  return retval;
}




int main()
{
  JavaVM* jvm;
  JavaVMInitArgs vm_args;
  JavaVMOption options[1];

  vm_args.version = JNI_VERSION_1_4;
  vm_args.nOptions = 1;
  options[0].optionString = "-Djava.class.path=.";
  vm_args.options = options;
  vm_args.ignoreUnrecognized = JNI_FALSE;

  JNIEnv * env;
  JNI_CreateJavaVM(&jvm, (void **)&env, &vm_args);

  jclass klass = (env)->FindClass("TestObject");
  assert( klass );

  jmethodID constructor = env->GetMethodID( klass, "<init>", "()V");
  assert( constructor );

  jobject obj = env->NewObject(klass, constructor );

  jmethodID test_method = (env)->GetMethodID( klass, "get_property", "(Ljava/lang/String;)LTestObject;" );
  assert( test_method );

  jvalue args[1];
  args[0].l = env->NewStringUTF("k");

  jobject rv = env->CallObjectMethodA(obj, test_method, args );

  jthrowable exc = env->ExceptionOccurred();
  if(exc)
  {
    env->ExceptionDescribe();
    env->ExceptionClear();
  }

  //TODO: do something with rv

}

like image 609
Michael Anderson Avatar asked Aug 01 '11 01:08

Michael Anderson


2 Answers

Normally the JVM expects to find native method definitions in a shared library that has been loaded via System#load or System#loadLibrary, and in most cases that is the most convenient approach. However, there does exist an alternative for situations like yours, where you would prefer to include the implementations directly in your executable.

If you call JNIEnv::RegisterNatives, you can instead pass the JVM a list of function pointers corresponding to the native methods in a particular class. When some Java code calls one of those methods, the JVM will know to invoke the function pointer you passed to RegisterNatives instead of searching through dynamically-loaded libraries.

JNINativeMethod methods[] = {
    {
        "frobFabulously",
        "(Ljava/lang/Object;)V",
        reinterpret_cast<void*>(NativeFrobFabulouslyImpl)
    },
};

env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(JNINativeMethod));
like image 175
Stuart Cook Avatar answered Nov 03 '22 10:11

Stuart Cook


It's been a while since I've messed with JNI, so I'm a little rusty on the topic. I think your problem is that you're declaring the get_property method as native. This means that the JVM expects to find a shared library exposing the get_property method. Here's the documentation on java.lang.UnsatisfiedLinkError.

UnsatisfiedLinkError is thrown when (1) attempting to call a native method that has not been loaded or (2) when loadLibrary or load method in Runtime or System is called for a file that cannot be found.

You declare a Java method native only if you're going to implement that method in C or C++ and then call it from Java. Since you're trying to do the opposite, i.e call Java methods from native code, you need to actually implement the get_property method in Java. In native code you'll then create a class instance of TestObject and call the get_property method on this instance.

I found a Sun tutorial on how to embed the JVM in native code. The book itself begins with examples of how to call native code from Java.

like image 2
Praetorian Avatar answered Nov 03 '22 10:11

Praetorian