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

时间:2018-01-15 16:49:33

标签: java c++ 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();
}

我在使用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);

        }
}

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

我还尝试在其上返回JNA Pointer类型并调用getString()方法。但打印乱码,在所有调用中都不一样。

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

在JNA文档here中提到,您应该在java中使用String作为C / C ++中相应的const char*

3 个答案:

答案 0 :(得分:6)

Caninonos&#39;答案充分解释了这个问题。这是两个不同的解决方案。

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

你必须以某种方式释放字符串,所以要正确地做。提供一个函数,它接收返回的指针并释放它。考虑将AutoCloseabletry-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);
}

爪哇

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

如果您选择使用AutoClosable,您可以从自定义PointerType中受益,JNA允许您将其用作Pointer的替代品。但是,由于您只是真正获得结果,因此最好将JNA接口封装在Java&#34; decryptor&#34;处理解放的课程。 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());
        }
    }
}

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

您可以使用输出参数,而不必记住释放动态分配的字符串。理想情况下,您希望使用size_t代替int,但使用它比JNA有点尴尬。如果你需要使用长于int max的字符串,请找出size_t

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

请注意,该函数会写入 no null terminator ,因此请确保使用返回的值。

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

爪哇

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

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

答案 1 :(得分:2)

正如评论中所提到的,问题源于悬空指针。换句话说,一旦C ++函数decrypt返回指向您的字符串的指针,该字符串就会在Java有机会访问它之前释放,从Java端访问时会导致未定义的行为。

解决问题最直接的方法是延长该字符串的生命周期。例如,通过使用strdup创建动态分配的副本。

尽管如此,虽然只是将return res.c_str();替换为return strdup(res.c_str());似乎有效,但它还有另一个问题:它会泄漏内存。实际上,必须在C ++中显式地和手动地释放动态分配的内存,但如果在Java端没有进行任何操作,则该字符串将位于内存中,直到程序终止。因此,一旦Java端完成此字符串,它需要通知C ++方不再需要它并且可以安全地释放(在free的情况下使用strdupthe documentation中提到,相同的逻辑适用于newdelete)。

为了做到这一点,您需要将C ++的调用结果存储在Java Pointer对象中,该对象将正确存储C ++的对象。后续调用char*所需的free(从Java端到C ++端)。

答案 2 :(得分:0)

此问题的另一个解决方案是使用回调函数:

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

        }
}

这也许不是最优雅的解决方案,但是一个关键的优势是,一旦处理完数据,您就不必处理内存释放。

为获得更多乐趣,还可以将回调函数包装在std::ostream中。当C ++代码已经利用<<运算符时,这特别有用:

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

}