0

On an Android application, I'm trying to get a string from native code up to Java, but exactly at the JNIEXPORT level, the std::string gets wiped out of its contents. Here's the code for all the three layers:

C++ code (original x-patform class):

std::string GTAInterface::GetConfigurationJSON()
{
    std::string m_cfgJSON = "a bare test";
    return m_cfgJSON;
}

C++ wrapper code (since JNI on Android can only call static C++ functions... no words on this):

const char *gtaGateway::GetConfigurationJSON(int sysId)
{
    string ret = ((GTAInterface*)gtaSystemArray[sysId])->GetConfigurationJSON();
    return ret.c_str(); // here the "ret" string is still retaining the retrieved value
}

An finally, the JNIEXPORT class:

JNIEXPORT jstring JNICALL
Java_com_gta_sdk_gtaGateway_GetConfigurationJSON(JNIEnv *env, jobject obj, jint sys)
{
    std::string cfgJson = gtaGateway::GetConfigurationJSON(sys);

    return env->NewStringUTF(cfgJson.c_str()); // here the debugger shows that "cfgJson" is "" (empty)??!!
}

So, the code correctly gets executed throughout all the call chain, but for some reason I can't figure out why the "cfgJson" string @ the JNIEXPORT level gets cleared out! Could someone please help out, since I have no other clue of what I could be doing wrong...

Many thanks!

gtrevi
  • 97
  • 1
  • 8
  • To keep the native objects in global container (**gtaSystemArray**) may be quite dangerous, the common practice is to pass such objects to Java as **long**, see for example https://stackoverflow.com/questions/337268/what-is-the-correct-way-to-store-a-native-pointer-inside-a-java-object. Anyways, there is nothing wrong in calling directly `return env->NewStringUTF(gtaGateway::GetConfigurationJSON(sys).c_str());` from **Java_com_gta_sdk_gtaGateway_GetConfigurationJSON()**. – Alex Cohn Jun 04 '17 at 09:11

2 Answers2

1

Your underlying string object is destroyed.

This code

const char *gtaGateway::GetConfigurationJSON(int sysId)
{
    string ret = ((GTAInterface*)gtaSystemArray[sysId])->GetConfigurationJSON();
    return ret.c_str(); // here the "ret" string is still retaining the retrieved value
}

returns a pointer to a C-style string, but per the c_str() method documentation

The pointer returned may be invalidated by further calls to other member functions that modify the object.

When your gtaGateway::GetConfigurationJSON() method returns, the destructor for the string object is called. That likely "modifies the object", I'd think.

I haven't tested it, but I suspect this code will work:

const string gtaGateway::GetConfigurationJSON(int sysId)
{
    string ret = ((GTAInterface*)gtaSystemArray[sysId])->GetConfigurationJSON();
    return ret;
}

Note the return is now a string object and not a C-string pointer into an object about to be destroyed.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
  • Hi Andrew, thanks for this suggestion! Actually, passing an std:string all the way up through the chain was one of my first attempts (sorry for not having mentioned it)... but unfortunately doing so gets even worst, since the debugger crashes into "memory" (_LIBCPP_MEMORY) with a "SIGSEGV - invalid address: fault address 0x0", so that's why I found the above "formula" that at least does not crash, even though with any added value :) – gtrevi Jun 03 '17 at 22:23
  • 1
    @gtrevi If you're seeing results like that, you're likely seeing the results of heap corruption, which unfortunately can be almost anywhere else in the program. This code might be just *finding* the land mine that was placed somewhere else by something like a double free or a buffer overrun. You probably should try running under a memory-checking program like Valgrind. See https://stackoverflow.com/questions/9216815/valgrind-and-java for how to do it under Java. – Andrew Henle Jun 04 '17 at 11:15
0

So, just caught the issue (thanks Andrew for inspiring): a through "heap chase" with a monitor and a load test got me to the corruption point (elsewhere in the application)... goods and bads of unmanaged :)

Therefore, just in case someone else needs it, the approach exposed in the question is perfectly working, as well as my initial attempt in passing std::string-s all the way up through the call chain 'till the jstring.

gtrevi
  • 97
  • 1
  • 8