0

I have some issue embedding the JVM in my C/++ program.

Startup and everything else seems to be fine, but, when I close a simple JVM with DestroyJavaVM() I get an error.

This error means that successive openings don't operate correctly - so - I need to fix it.

I'm installing another JVM to see if it's just OpenJ9 but that seems likely.

Attached is an example using the "Catch" header that does/should compile with only the jni headers and run if JAVA_HOME points correctly on Windows.

edited to include Remy Lebeau's advice

// https://github.com/catchorg/Catch2/tree/Catch1.x
// https://github.com/philsquared/Catch/releases/download/v1.12.2/catch.hpp
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

#pragma once
#pragma warning( push )
#pragma warning( suppress : 26451 )
#pragma warning( suppress : 26495 )
#pragma warning( suppress : 26812 )
#include <jni.h>
#pragma warning( pop )

using create_java_vm_t = jint(JNICALL*)(JavaVM**, void**, void*);

TEST_CASE("a minimum example for the internet", "[paljni]")
{
    // taken from
    // https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#wp9502
    //

    /// (this works fine)
    // find the jvm functions - this works fine
    static create_java_vm_t create_java_vm = nullptr;
    if (!create_java_vm)
    {
        auto path = std::string(getenv("JAVA_HOME")) + "bin/default/jvm.dll";

        auto dll = LoadLibrary(path.c_str());
        REQUIRE(dll);

        create_java_vm = reinterpret_cast<create_java_vm_t>(GetProcAddress(dll, "JNI_CreateJavaVM"));

        REQUIRE(nullptr != create_java_vm);
    }

    /// (this works fine)
    // compute the args
    JavaVMInitArgs args = {};
    args.version = JNI_VERSION_1_6;

    std::vector<JavaVMOption>options;

    // options.emplace(options.end())->optionString = "-verbose:jni";
    options.emplace(options.end())->optionString = "-verbose:init"; // shows a lot of status messages that seem to suggest all threads close fine
    // options.emplace(options.end())->optionString = "-Xcheck:jni:warn";
    // options.emplace(options.end())->optionString = "-Xcheck:jni:advice";
    // options.emplace(options.end())->optionString = "-Xcheck:jni:pedantic";


    args.options = options.data();
    args.nOptions = options.size();

    /// (this works fine)
    // create the vm
    JavaVM* jvm;
    JNIEnv* env;
    const auto create_ret = create_java_vm(&jvm, (void**)&env, &args);
    REQUIRE(JNI_OK == create_ret);

    ///
    // clean up the vm
    //
    // THIS DOES NOT WORK! IT RETURNS (-1)
    //
    const auto done = jvm->DestroyJavaVM();
    REQUIRE(JNI_OK == done);

    FAIL("The test doesn't reach this point");
}
pal
  • 942
  • 1
  • 9
  • 19

1 Answers1

1

One problem I see is you are missing JNICALL on your function declarations, per jni.h:

//-----------------------------
                              \/ here
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_GetDefaultJavaVMInitArgs(void *args);

//-----------------------------
                              \/ here
_JNI_IMPORT_OR_EXPORT_ jint JNICALL
JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);

On Windows, JNICALL resolves to the __stdcall calling convention. However, most C/C++ compilers default to __cdecl instead. Calling convention mismatches are undefined behavior and can wreak havoc on the call stack.

I would also suggest using typedef or using to create aliases for the function types.

Try this:

#define CATCH_CONFIG_MAIN
//
// https://github.com/catchorg/Catch2/tree/Catch1.x
// https://github.com/philsquared/Catch/releases/download/v1.12.2/catch.hpp
#include "catch.hpp"

#pragma once
#pragma warning( push )
#pragma warning( suppress : 26451 )
#pragma warning( suppress : 26495 )
#pragma warning( suppress : 26812 )
#include <jni.h>
#pragma warning( pop ) 

TEST_CASE("a minimum example for the internet", "[paljni]")
{
    //
    // https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#wp9502
    //

    ///
    // find the jvm functions - this works fine

    //typedef jint (JNICALL *create_java_vm_func)(JavaVM**, void**, void*);
    //typedef jint (JNICALL *default_vm_args_func)(void*);
    using create_java_vm_func = jint (JNICALL *)(JavaVM**, void**, void*);
    using default_vm_args_func = jint (JNICALL *)(void*);

    static create_java_vm_func create_java_vm = nullptr;
    static default_vm_args_func default_vm_args = nullptr;

    if (!create_java_vm)
    {
        //auto path = std::string(getenv("JAVA_HOME")) + "bin/server/jvm.dll";
        //auto path = std::string(getenv("JAVA_HOME")) + "bin/default/jvm.dll";
        auto path = std::string(getenv("JAVA_HOME")) + "bin/j9vm/jvm.dll";
        
        auto dll = LoadLibrary(path.c_str());
        REQUIRE(dll);

        {
            auto proc_address = GetProcAddress(dll, "JNI_CreateJavaVM");
            REQUIRE(proc_address);
            create_java_vm = reinterpret_cast<create_java_vm_func>(proc_address);
        }
        {
            auto proc_address = GetProcAddress(dll, "JNI_GetDefaultJavaVMInitArgs");
            REQUIRE(proc_address);
            default_vm_args = reinterpret_cast<default_vm_args_func>(proc_address);
        }
    }

    ///
    // compute the args
    JavaVMInitArgs args = {};
    args.version = JNI_VERSION_1_6;

    const auto version_ret = default_vm_args(&args);
    REQUIRE(JNI_OK == version_ret);

    ///
    // create the vm
    JavaVM* jvm = nullptr;
    JNIEnv* env = nullptr;
    const auto create_ret = create_java_vm(&jvm, (void**)&env, &args);
    REQUIRE(JNI_OK == create_ret);


    ///
    // just run the GC method - it's there and easy to invoke
    // ... it works fine, so, one can assume the VM was setup as expected
    {
        auto sys = env->FindClass("java/lang/System");
        auto gc = env->GetStaticMethodID(sys, "gc", "()V");
        env->CallStaticVoidMethod(sys, gc);
    }

    ///
    // clean up the vm
    const auto done = jvm->DestroyJavaVM();
    REQUIRE(JNI_OK == done);


    FAIL("i guess it's working now");
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • didn't seem to do anything different. i'll update the question (after work) with the conventions and `using` things as they're quite nice – pal Apr 21 '23 at 11:09