Invoking a JVM through JNI

I am trying to create a native application, that invokes a JVM. It compiles fine now, but doesn’t execute. When I try to execute the resulting native executable “tester.exe”, it fails with the following message:


Error occurred during initialization of VM
Unable to load native library: Can't find dependent libraries

I have no idea, what dependent libraries are missing and how I can tell the program to find them.

This is the code of the native program:


    JavaVM* jvm; // denotes a Java VM
    JNIEnv* env; // pointer to native method interface
    JavaVMInitArgs vm_args; // JDK/JRE 6 VM initialization arguments
    JavaVMOption* options = new JavaVMOption[1];
    //options[0].optionString = "-Djava.library.path=C:\\Programme\\Java\\jre6\\bin;.;C:\\WINDOWS\\Sun\\Java\\bin;C:\\WINDOWS\\system32;C:\\WINDOWS;C:/Programme/Java/jre6/bin/client;C:/Programme/Java/jre6/bin;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem";
    //options[0].optionString = "-Djava.library.path=C:\\Programme\\Java\\jre6\\bin";
    options[0].optionString = "-Djava.class.path=D:\\rfdynhud.jar";
    vm_args.version = JNI_VERSION_1_6;
    vm_args.nOptions = 1;
    vm_args.options = options;
    vm_args.ignoreUnrecognized = false;
    // load and initialize a Java VM, return a JNI interface pointer in env
    JNI_CreateJavaVM( &jvm, (void**)&env, &vm_args ); // This is where the program exists with exit code 1. The Process is terminated here. The next line won't be executed.
    //printf( "%u\n", ret );
    //if ( ret == 0 )
        return;
    //delete options;
    // invoke the Main.test method using the JNI
    //jclass cls = env->FindClass( "net/ctdp/rfdynhud/RFDynHUD" );
    //jmethodID mid = env->GetStaticMethodID( cls, "dumpSomething", "(I)V" );
    //env->CallStaticVoidMethod( cls, mid, 123 );
    // We are done.
    //jvm->DestroyJavaVM();

Any clues? I am completely lost.

Marvin

You seem to have tried everything, except …\jre\lib

???

Do you mean “…\jre\lib”? I tried that as well as “jre\lib”. It doesn’t work.

I just used Dependency Walker on the jvm.dll and it says, that it misses three dlls called ieshims.dll, wer.dll and efsadu.dll. Does anybody know these dlls and where I can (savely) get them?

Marvin

afaik, ieshims.dll, wer.dll - Windows Vista files

I don’t have Vista (using XP), nor have I enabled encryption. So I assume, these DLLs shouldn’t needed and their missing is not the cause of my problems.

Has anybody every tried to use the invocation framework?

Marvin

I use JNI invocation for all my games and they work fine AFAIK.

Cas :slight_smile:

Like he wants to? Invoking a JVM from a C/C++ program and not the other way around? The native aplication is the host in this case…

Yes. Very most tutorials deal with the other way round (a Java program defines a native method and is the host). And I can find absolutely not a single tutorial about how to correctly setup a Visual C++ project for that or how to configure your environment.

It is also strange, that no tutorial describes, how to compile or write your C++ program, so that it finds the Java dlls in the installation path.

@princec: If you really use invocation, could you possibly give kick to get started?

Marvin

Perhaps you could have a look at a java executable wrapper to get started, they invoke the JVM from C code.
http://launch4j.sourceforge.net/

Er, no. My games are Windows executables that invoke a JVM.

Cas :slight_smile:

Like this: (warning: crap code alert)


/*
 * Droid Assault launcher
 */

#include "launcher.h"

#define OPTIONS 7

static JNIEnv *env;
static jobject jobj = 0;
static JavaVM *vm;

/*
 * Display an error message
 */
void messageBox(char * message) {
	MessageBox(NULL, message, APP_TITLE, MB_OK);
}

typedef jint (APIENTRY * CreateJavaVMPROC) (JavaVM **pvm, void **penv, void *args);

/*
 * Invoke our embedded JVM
 */
int WINAPI WinMain
(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow
) 
{
    jint res;
	jclass cls;
    jmethodID mid;
    jobjectArray args;
    JavaVMInitArgs vm_args;
    JavaVMOption options[OPTIONS];

	options[0].optionString = "-Djava.class.path=patch.jar;DroidAssault.jar;gamecommerce.jar;spgl-lite.jar;common.jar;jorbis.jar;lwjgl.jar;lwjgl_util.jar;jinput.jar;music.jar;resources.jar";
	options[1].optionString = "-Dworkdir=.";
	options[2].optionString = "-Xms96m";
	options[3].optionString = "-Xmx96m";
	options[4].optionString = "-XX:MaxGCPauseMillis=5";
	options[5].optionString = "-XX:+UseAdaptiveSizePolicy";
	options[6].optionString = "-Xincgc";

	HMODULE jvmdll = LoadLibrary("bin\\client\\jvm.dll");
	if (jvmdll == NULL) {
		messageBox("Failed to load Java dll.");
	}

    vm_args.version = JNI_VERSION_1_2;
    vm_args.options = options;
    vm_args.nOptions = OPTIONS;
    vm_args.ignoreUnrecognized = TRUE;

	CreateJavaVMPROC CreateJavaVM = (CreateJavaVMPROC) GetProcAddress(jvmdll, "JNI_CreateJavaVM");

    res = CreateJavaVM(&vm, (void **)&env, &vm_args);

    if (res < 0) {
		messageBox("Failed to create Java virtual machine.");
        return 0;
    }

    // Get the main class
    cls = env->FindClass(MAIN_CLASS);

    if (cls == 0) {
        messageBox("Failed to find the main class.");
        return 0;
    }

    // Get the method ID for the class's main(String[]) function.
    mid = env->GetStaticMethodID(cls, "main", "([Ljava/lang/String;)V");

    if (mid == 0) {
        messageBox("Failed to find the main method.");
        return 0;
    }
    args = env->NewObjectArray(0, env->FindClass("java/lang/String"), NULL);

    // Run the main class...
    env->CallStaticVoidMethod(cls, mid, args);

	return 1;

}

Cas :slight_smile:

Thank you so much, Cas. This is exactly, what I needed and it really works.

I have a few new questions:

Do I have to unload a library loaded through LoadLibrary()? I couldn’t find a function for that.

The installation path of the jre is not in my PATH environment variable not anywhere else, where it could be found through standard searches, so I have to use the full path for LoadLibrary() to load msvcr71.dll and jvm.dll. I could inspect the registry to find the path. But I guess, there’s some common way to find the java installation path, is there? I am using a standard java installation (no modified paths, etc.).

Is it important, whether I use JNI_VERSION_1_2 or JNI_VERSION_1_6?

Marvin

No idea about the difference between 1_2 and 1_6. There probably exists an API to get the path to the currently installed runtime but I don’t know what it is and in any case I don’t trust existing runtimes, I always embed the JVM installation privately and use that, thus bypassing the problem (and many others) completely. But you can otherwise just look in the system registry - the Sun JVMs stash the path in there, but it’s in HKLM rather than HKCU and therefore may require elevated privileges to read on Vista.

I’ve never bothered unloading a library but then I never need to do anything fancy on app exit, I just quit the process and the OS takes care of cleanup.

Cas :slight_smile:

ok, thank you very much.

Marvin

I’m sorry, but I have a new question.

I have a simple example class with an instance method as follows:


public class MyClass
{
    public int getInt()
    {
        return ( 1234567 );
    }
}

In my native (invocation) code I first get the class and the do the following:


jmethodID mid = env->GetMethodID( cls, "<init>", "(II)V" );

if ( mid == 0 )
{
    messageBox( "Failed to find the constructor method." );
    return ( FALSE );
}

jobject obj = env->NewObject( cls, mid, 123, 456 );

mid = env->GetMethodID( cls, "getInt", "()I" );

if ( mid == 0 )
{
    messageBox( "Failed to find the getInt() method." );
    return ( FALSE );
}

jint i = env->CallIntMethod( obj, mid );
std::cout << i; std::cout << "\n";

The returned integer is always 0 (zero). If I try the same with a method retuning an object, the object will always be NULL.

The same will work perfectly with a static method. What am I doing wrong?

Marvin

You never checked obj to see if you got something or not. And what I suspect you’ll find is that it’s null, and you’ve got an exception pending, which is that you didn’t have access to the constructor, which you’ve not defined and is therefore package private :slight_smile:

I am a human compiler 8)

Cas :slight_smile:

You typed wrong signature for default constructor, should be “()V”.

It works now. There was indeed something going wrong in the constructor.

Is there a way to read the last exception’s message in the native code?

Thanks a lot again.

Marvin

Look at: ExceptionOccurred, ExceptionDescribe & ExceptionClear in the JNI ref.

A broken human compiler :wink:

With no constructors defined the default no-args constructor is public not package private.