Limnic
Limnic

Reputation: 1876

JNI getObjectClass crashes VM

I am trying to make a thing where C++ does logging to the java side via JNI.

So far I have a native setter:

public native void setLogger(Logger logger);

which I implement in C++:

void __stdcall Java_setLogger(JNIEnv* env, jobject, jobject loggerInstance)
{
    logger = Logger(env, &loggerInstance);
}

The constructor of the Logger class is as follows:

Logger::Logger(JNIEnv* env, jobject* loggerInstance)
{
    this->env = env;
    this->loggerInstance = *loggerInstance;
}

When I try to call a java method via JNI on the provided logger instance (from Java) it crashes the VM. I am not sure why because this is pieced together from other stackoverflow questions.

void Logger::debug(const std::string code, const std::string message) const
{
    std::cout << "debug!" << std::endl; //for debugging purposes

    jclass loggerClass = env->GetObjectClass(loggerInstance);
    std::cout << "class retrieved" << std::endl; //for debugging purposes

    if (loggerClass == NULL)
    {
        std::cout << "logger class null" << std::endl;
        return;
    }

    jmethodID debugMethod = env->GetMethodID(loggerClass, "debug", "(Ljava/lang/String;Ljava/lang/String;)V");
    std::cout << "method retrieved" << std::endl; //for debugging purposes

    if (debugMethod == NULL)
    {
        std::cout << "debug method null" << std::endl;
        return;
    }

    env->CallVoidMethod(loggerInstance, debugMethod, code, message);
}

The console output:

debug!
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000067dbef23, pid=6812, tid=0x0000000000003354

The debug method on the Java side's logger is:

public final void debug(String code, String message) {
    ...
}

Finally, the error log file produced mentions:

Internal exceptions (2 events):
Event: 0.041 Thread 0x0000000000d9e800 Exception <a 'java/lang/NoSuchMethodError': Method sun.misc.Unsafe.defineClass(Ljava/lang/String;[BII)Ljava/lang/Class; name or signature does not match> (0x0000000780987ca8) thrown at [C:\re\workspace\8-2-build-windows-amd64-cygwin\jdk8u121\8372\hotspot\
Event: 0.041 Thread 0x0000000000d9e800 Exception <a 'java/lang/NoSuchMethodError': Method sun.misc.Unsafe.prefetchRead(Ljava/lang/Object;J)V name or signature does not match> (0x0000000780987f90) thrown at [C:\re\workspace\8-2-build-windows-amd64-cygwin\jdk8u121\8372\hotspot\src\share\vm\prims

Perhaps I should mention that the Java side is technically not Java; but it is Kotlin.


UPDATE (SOLVED):

So, I did as the answer below said but then I noticed I didn't even get to the "debug!" cout. I was very confused but then I realized that in my destructor, I always call env->deleteGlobalRef(..) and that I have a necessary default constructor (that initializes nothing) to be able to declare Logger logger; and initialize it later in C++.

Simply adding if(loggerInstance != NULL) before deleting the global ref solved everything. This is because C++ (visual c++) calls the destructor when I assign a new value, and it actually assigns a new instance when putting Logger logger; somewhere (from my observations anyways).

Upvotes: 1

Views: 1521

Answers (2)

tommybee
tommybee

Reputation: 2579

I think you have to use jstring not a c++ string itself

void Logger::debug(const std::string code, const std::string message) const
{
    std::cout << "debug!" << std::endl; //for debugging purposes

    jclass loggerClass = env->GetObjectClass(loggerInstance);
    std::cout << "class retrieved" << std::endl; //for debugging purposes

    if (loggerClass == NULL)
    {
        std::cout << "logger class null" << std::endl;
        return;
    }


    jstring javMessage = env->NewStringUTF((const char* )message.c_str());
    jstring javCode = env->NewStringUTF((const char* )code.c_str());

    jmethodID debugMethod = env->GetMethodID(loggerClass, "debug", "(Ljava/lang/String;Ljava/lang/String;)V");
    std::cout << "method retrieved" << std::endl; //for debugging purposes

    if (debugMethod == NULL)
    {
        std::cout << "debug method null" << std::endl;
        return;
    }

    env->CallVoidMethod(loggerInstance, debugMethod, javCode, javMessage);
}

Upvotes: 0

Jorn Vernee
Jorn Vernee

Reputation: 33885

The garbage collector can move around objects on the Java side, so if you save your logger object like that: this->loggerInstance = *loggerInstance, it might be moved in between JNI calls, so when you try to use it in GetObjectClass the pointer might be dangling.

A safe way to store a pointer to a Java object is to use NewGlobalRef which creates a pin on the object:

this->loggerInstance = env->NewGlobalRef(*loggerInstance);

Just make sure to delete it again with DeleteGlobalRef when you're done using it (So apply rule of 5).


I will also point out that your last call: env->CallVoidMethod(loggerInstance, debugMethod, code, message); will not work. There is no automagical marshalling from an std::string to a java.lang.String. You could call NewStringUTF to create a Java String, and pass that.

Upvotes: 4

Related Questions