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++.
Caninonos' answer explains the problem sufficiently. Here's two different solutions.
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());
}
}
}
decrypt
function to accept an output bufferInstead 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.