从本机代码返回"const char*"并在java中获取"字符串"
Returning `const char*` from native code and getting `String` in java
我正在使用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 的打印值会打印一个空字符串。我已经看到了这个问题,但它为 JNI 提供了解决方案。我想使用 JNA 并返回一个字符串。我应该怎么做?
我还尝试返回 JNAPointer
类型并在其上调用getString()
方法。但是打印乱码,这在所有调用中都不相同。
我确实知道我在函数作用域中返回了一个悬空指针,当它到达 java 调用时会被销毁。我想要一个简单的解决方案,我可以使用 JNA 将字符串从C++代码返回到 Java。
这里在 JNA 文档中提到,您应该在 Java 中使用String
来实现 C/C++ 中的相应const char*
。
Caninonos的回答充分解释了这个问题。这是两种不同的解决方案。
A) 动态分配字符串并提供释放字符串的函数
你将不得不以某种方式释放字符串,所以正确地做。提供一个函数,该函数获取返回的指针并将其释放。请考虑将AutoCloseable
与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);
}
爪哇岛
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"解密器"类中。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
.
由于您使用的是三重 DES,因此它可能会应用填充,因此输出的大小可能与输入的长度不同。为了解决这个问题,如果缓冲区太小,该函数会输出所需的大小。
请注意,该函数不写入 null 终止符,因此请确保使用返回的值。
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++函数中完全删除。
正如评论中提到的,问题源于一个悬空的指针。换句话说,一旦 C++ 函数decrypt
返回指向字符串的指针,该字符串就会在 Java 有机会访问它之前被释放,从而导致从 Java 端访问时出现未定义的行为。
解决问题的最直接方法是延长该字符串的生存期。例如,通过使用strdup
创建动态分配的副本。
也就是说,虽然只是用看似有效的return strdup(res.c_str());
替换return res.c_str();
,但它遇到了另一个问题:它泄漏内存。实际上,动态分配的内存必须在C++中显式手动释放,但如果在 Java 端什么都不做,该字符串将保留在内存中,直到程序终止。因此,一旦 Java 端处理完此字符串,它需要通知C++端不再需要它并且可以安全地释放(free
在文档中提到的strdup
的情况下,相同的逻辑将适用于new
和delete
)。
为此,您需要将C++的调用结果存储在 JavaPointer
对象中,该对象将正确存储后续调用free
所需的C++char*
(从 Java 端到C++端)。
这个问题的另一个解决方案是使用回调函数:
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());
}
}
爪哇岛:
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;
}
}
- 在java中解决这段代码时面临循环中的问题
- 尝试用java代码编译和运行c++代码
- 在这种情况下,java对象是否可以调用本机函数
- 在java中读取c++字节的位字段
- 为什么C++对链表中的下一个节点使用指针,而像 C# 或 Java 这样的语言只使用类 Node 的名称?
- 使用已使用 java 编码的 openssl 解码数据
- SWIG Java 在使用 -DSWIGWORDSIZE64 时将int64_t转换为 jlong
- Android Java USB for native cpp
- 在由Sublime文本3编译后在cmd上显示Java程序输出
- C++ equivalent to Java Map getOrDefault?
- C++ 中的 Java 样式枚举
- 是否有技术原因阻止 Java 中的 final C++ 像 const 一样严格?
- 加密在 Windows、C++ 和 Java 中传输中的数据
- Java从C++回调到C++回调
- 在 c++ 中模拟输入并在 JAVA 中读取它?
- 用C++包装 Java 库 (JNI)
- 使用 TreeSet Java 对反转进行计数
- 验证openssl c++中的签名,这是由JAVA DSA签名的?
- 如何通过 JNI 将 C 字符串表情符号传递给 Java
- 如何从保存在 Java 中C++的字节数组中读取数字?