Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning `const char*` from native code and getting `String` in java

I'm using JNA to interface my C++ code with java. I have a native function which takes a string as input and returns a string as output. Following is the C++ implementation of the function.

const char* decrypt(char* value){
    std::string res = TripleDes::getInstance().decrypt(value);
    std::cout<<res.c_str()<<"\n";
    return res.c_str();
}

I'm loading this function in a simple java program using JNA and trying to get a string from java. The problem is, I'm getting an empty string from java. Following is the java code:

interface NativeExample extends Library {
    NativeExample ne = (NativeExample) Native.loadLibrary("foo", NativeExample.class);
    String decrypt(String value);
}

public class Main{

        public static void main(String[] args){

                String decrypted =  NativeExample.ne.decrypt("foo");
                System.out.println(decrypted);

        }
}

The printed values from the C++ code are perfect but from Java, an empty string is printed. I've seen this question but it gives a solution for JNI. I want to use JNA and return a string. How should I go about this?

I also tried to return JNA Pointer type and called getString() method on it. But prints gibberish which is not same across all invocations.

I do understand that I'm returning a dangling pointer in function scope which would get destroyed by the time it reaches java invocation. I want a simple solution to which I can return a String from C++ code to Java using JNA.

It is mentioned in the JNA documentation here that you should use String in java for the corresponding const char* in C/C++.

like image 225
Saqib Ahmed Avatar asked Jan 15 '18 16:01

Saqib Ahmed


3 Answers

Caninonos' answer explains the problem sufficiently. Here's two different solutions.

A) Dynamically allocate a string and provide a function for freeing it

You're going to have to free the string somehow, so do it properly. Provide a function which takes the pointer returned and frees it. Consider using AutoCloseable with try-with-resources statements.

C++

char* decrypt(char* value) {
    std::string res = TripleDes::getInstance().decrypt(value);
    std::cout << res.c_str() << "\n";
    return strndup(res.c_str(), res.size());
}

void free_decrypted_string(char* str) {
    free(str);
}

Java

interface NativeExample extends Library {
    NativeExample ne = (NativeExample) Native.loadLibrary("foo", NativeExample.class);

    Pointer decrypt(String value);
    void free_decrypted_string(Pointer str);
}

public class Main {
    public static void main(String[] args) {
        Pointer decrypted = NativeExample.ne.decrypt("foo");
        System.out.println(decrypted.getString(0));
        NativeExample.ne.free_decrypted_string(decrypted);
    }
}

In case you choose to utilize AutoClosable, you could benefit from a custom PointerType which JNA allows you to use as an almost drop-in replacement for Pointer. However, since you're only really just getting the result, it might be better to encapsulate the JNA interface in a Java "decryptor" class which deals with the freeing. An AutoClosable would be better suited for things like file or process handles.

interface NativeExample extends Library {
    NativeExample ne = (NativeExample) Native.loadLibrary("foo", NativeExample.class);

    FreeableString decrypt(String value);
    void free_decrypted_string(FreeableString str);

    class FreeableString extends PointerType implements AutoCloseable {
        @Override
        public void close() {
            ne.free_decrypted_string(this);
        }
        public String getString() {
            return this.getPointer().getString(0);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        try (NativeExample.FreeableString decrypted = NativeExample.ne.decrypt("foo")) {
            System.out.println(decrypted.getString());
        }
    }
}

B) Change the decrypt function to accept an output buffer

Instead of having to remember to release the dynamically allocated string, you could use output parameters. Ideally you'd want to use size_t instead of int, but using it is a bit awkward from JNA. If you need to work with strings longer than int max, figure out size_t.

Since you're using Triple DES, it may apply padding so the size of your output may differ from the input's length. To get around this, the function outputs the required size if the buffer was too small.

Notice that the function writes no null terminator, so make sure you use the returned value.

C++

int decrypt(const char *value, char *output, int *output_size) {
    std::string res = TripleDes::getInstance().decrypt(value);
    std::cout << res.c_str() << "\n";

    if (*output_size < res.size()) {
        *output_size = res.size();
        return 0;
    }

    size_t bytes_written = res.copy(output, *output_size);
    return (int)bytes_written;
}

Java

interface NativeExample extends Library {
    NativeExample ne = (NativeExample) Native.loadLibrary("foo", NativeExample.class);
    int decrypt(String value, byte[] output, IntByReference outputBufferSize);
}

public class Main {
    public static void main(String[] args) {
        byte[] buffer = new byte[4096];
        IntByReference bufferSize = new IntByReference(buffer.length);

        int bytesWritten = NativeExample.ne.decrypt("foo", buffer, bufferSize);
        if (bytesWritten == 0 && bufferSize.getValue() > buffer.length) {
            // buffer was too small for decrypted output
            buffer = new byte[bufferSize.getValue()];
            bytesWritten = NativeExample.ne.decrypt("foo", buffer, bufferSize);
        }

        String decrypted = new String(buffer, 0, bytesWritten);
        System.out.println(decrypted);
    }
}

If you always know the size of your output, you can simplify the call to ignore the updated required buffer size, or drop it from the C++ function completely.

like image 80
cbr Avatar answered Oct 14 '22 12:10

cbr


As mentioned in the comments, the problem stems from a dangling pointer. In other words, once the C++ function decrypt returns the pointer to your character string, that string is released before Java has a chance to access it, causing undefined behaviour when accessed from the Java side.

The most straightforward way to solve the problem is then to extend the lifetime of that character string. For instance, by creating a dynamically allocated copy with strdup.

That said, while merely replacing return res.c_str(); by return strdup(res.c_str()); seemingly works, it suffers from another problem: it leaks memory. Indeed, dynamically allocated memory must explicitly and manually be freed in C++ but if nothing is done on the Java side, that string will sit in memory until your program terminates. Therefore, once the Java side is done with this string, it needs to notify the C++ side that it's no longer needed and can be safely released (with free in the case of strdup as mentioned in the documentation, the same logic would apply to new and delete).

In order to do that, you'll need to store the C++'s call result in a Java Pointer object which will correctly store the C++'s char* necessary for the subsequent call to free (from the Java side to the C++ side).

like image 2
2 revs Avatar answered Oct 14 '22 11:10

2 revs


Yet another solution to this problem is to use a callback function:

C++:

typedef void (*callback_t) (const char*);

extern "C" {

void decrypt(char* value, callback_t output_func){
    std::string res = TripleDes::getInstance().decrypt(value);
    std::cout<<res.c_str()<<"\n";
    output_func(res.c_str());
}

}

Java:

private interface MyCallback extends Callback {
    public void callback(String val);
}

interface NativeExample extends Library {
    NativeExample ne = (NativeExample) Native.loadLibrary("foo", NativeExample.class);
    String decrypt(String value);
}

public class Main{

        public static void main(String[] args){
                StringBuilder decryptedBuilder = new StringBuilder();
                String decrypted =  NativeExample.ne.decrypt("foo", decryptedBuilder::append);
                System.out.println(decryptedBuilder.toString());

        }
}

It's perhaps not the most elegant solution, but a key advantage is that you don't have to deal with memory deallocation once you're done with the data.

For even more fun it's also possible to wrap the callback function in a std::ostream. This is particularly useful for when the C++ code already utilises the << operator:

C++:

#include <iostream>
#include <sstream>

typedef void (*callback_t) (const char*);

class callback_buffer : public std::stringbuf
{
    private:
        callback_t* func;
    public:
        callback_buffer(callback_t* func): func(func){}

        ~callback_buffer()
        {
            sync();
        }

        int sync()
        {
            (*func)(str().c_str());
            str("");
            return 0;
        }
};

extern "C" {

void decrypt(char* value, callback_t output_func){
    std::string res = TripleDes::getInstance().decrypt(value);
    std::cout<<res.c_str()<<"\n";
    callback_buffer buf(&func);
    std::ostream stream(&buf);
    stream << res;
}

}
like image 1
Paul Avatar answered Oct 14 '22 11:10

Paul