从本机代码返回`const char*`并在java中获取`String`

Saq*_*med 3 c++ java java-native-interface shared-libraries jna

我正在使用JNA将我的C++代码与java连接起来.我有一个本机函数,它接受一个字符串作为输入并返回一个字符串作为输出.以下是该函数的C++实现.

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

我正在使用JNA在一个简单的java程序中加载此函数,并尝试从java获取一个字符串.问题是,我从java得到一个空字符串.以下是java代码:

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);

        }
}
Run Code Online (Sandbox Code Playgroud)

来自C++代码的打印值非常完美,但是从Java中打印出一个空字符串.我已经看到了这个问题,但它为JNI提供了解决方案.我想使用JNA并返回一个字符串.我该怎么办呢?

我也尝试返回JNA Pointer类型和调用getString()方法.但打印乱码,这在所有调用中都不相同.

我知道我在函数范围内返回一个悬空指针,它会在到达java调用时被破坏.我想要一个简单的解决方案,我可以使用JNA将C++代码中的String返回给Java.

这里的JNA文档中提到,你应该String在java中使用const char*C/C++中的相应对象.

cub*_*brr 6

Caninonos的回答充分解释了这个问题.这是两种不同的解决方案.

A)动态分配字符串并提供释放它的功能

你将不得不以某种方式释放字符串,所以要正确地做.提供一个函数,它接收返回的指针并释放它.考虑使用AutoCloseablewith try-with-resources语句.

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);
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您选择使用AutoClosable,您可以从PointerTypeJNA允许您作为几乎免费替代的自定义中受益Pointer.但是,由于您只是刚刚获得结果,因此最好将JNA接口封装在处理释放的Java"decryptor"类中.一个AutoClosable会更适合搞什么文件或进程句柄.

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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

B)更改decrypt函数以接受输出缓冲区

您可以使用输出参数,而不必记住释放动态分配的字符串.理想情况下,你想要使用size_t而不是int,但使用它有点尴尬JNA.如果你需要使用长于int max的字符串,那就搞清楚吧size_t.

由于您使用的是Triple DES,因此它可能会应用填充,因此输出的大小可能与输入的长度不同.为了解决这个问题,如果缓冲区太小,函数会输出所需的大小.

请注意,该函数不写空终止符,因此请确保使用返回的值.

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;
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您始终知道输出的大小,则可以简化调用以忽略更新的所需缓冲区大小,或者完全从C++函数中删除它.