返回结构并进一步清理

问题描述 投票:0回答:1

我们正在构建一个在多种上下文中使用的 C++ 库,Java/Kotlin 就是其中之一。我们非常广泛地使用 JNI,但最近我们认为它周围有很多样板文件,并且认为 JNA 可能是一个更好的主意。由本机代码调用 Java 回调真的很好——对象的生命周期不再是问题。在尝试实现我假设的“名义”案例时,我们遇到了瓶颈。假设我们有以下 C++ 结构将由本机函数“按值”返回并在 Java 中使用:

#include <string>
#include <vector>

struct MyCustomStruct {
  int x;
  std::vector<std::string> lines;
};

JNA需要C联动,不懂C++,所以我相应地调整了结构。此外,我们不能在返回结构时天真地复制指针,而是深复制字符串,因为一旦堆栈展开,我们就会松开字符串:

#include <cstdlib>
#include <string>
#include <vector>

extern "C" {
struct MyCustomStruct {
  int x;
  char** lines;
  int linesCount;
};

MyCustomStruct getMyCustomStructInstance() {
  std::vector<std::string> lines{"abc", "xyz"};
  MyCustomStruct result;

  result.lines = (char**)malloc(lines.size());
  for(size_t i = 0; i < lines.size(); i++) {
    result.lines[i] = strdup(s.c_str());
  }
  
  result.x = 123;
  result.linesCount = lines.size();
  return result;
}
}

以及对应的Java代码:

package my.native.package;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import java.util.List;
import java.util.Arrays;
import com.sun.jna.ptr.PointerByReference;

public interface MyNativeCLibrary extends Library {
  public MyCustomStruct getMyCustomStructInstance();

  public static class MyCustomStruct extends Structure {
    public static class ByValue extends MyCustomStruct implements Structure.ByValue {}

    public int x;
    public PointerByReference lines;
    public int linesCount;

    public String[] linesAsArray() {
      return lines.getPointer().getStringArray(0);
    }

    @Override
    protected List getFieldOrder() {
      return Arrays.asList(new String[] { "x", "lines", "linesCount" });
    }
  }
}

现在,在调用方,我需要存储该字符串集合(如:它是在整个 Java 应用程序的生命周期内保存在内存中的静态变量),因此深度复制它也是明智的。复制这个

PointerByReference
听起来很糟糕,但对于习惯 C++ 和处理原始字节的人来说是可行的。我将很难向我的客户解释用法,但是,好吧,我远程想象它。

现在,直截了当:我们已经通过

strdup
分配了一些内存,现在清理它是有意义的(考虑到我们已经在 Java 端获得了深度复制)。按照 eshayne 的示例,我添加了清理方法。最后,用法如下所示:

MyCustomStruct foo = myLibraryHandle.getMyCustomStructInstance();
Application.foo = deepCopyMyCustomStruct(foo);
myLibraryHandle.cleanUpMyCustomStruct(foo);

不过,这看起来很可怕。尽管我们在 JNA 类之上有一个 Java 包装器,它抽象了所有这些技术细节,但我无法想出一种公开此类功能的“原子”方式。

有没有更好的机制?我觉得必须有一种更自然的方式来做到这一点。

我想用某种 lambda 来包装这种“不安全”的调用:

myLibraryHandle.unsafeContext(() -> { myLibraryHandle.getMyCustomStructInstance(); }

但是如果

unsafeContext
接受任何 void 函数,则没有元方法可以知道内部调用了哪些函数。

我还尝试在本机函数之后立即调用“cleanup”(有点希望 JNA 能够深度复制...),但惨败。

java c++ java-native-interface jna
1个回答
0
投票

如果你可以使用 strdup,这意味着你的字符串中没有零字节。

[ 1].这使得实现

slightly

 在两端都更加笨拙,但(1)更容易在概念上理解,(2)更高效的缓存,(3)更容易清理,因为所有的释放都可以用一次调用替换
char**
 在你的
char*

[1] 你可能天真地使用两个连续的零字节来指示字符串的结尾,但这会阻止你有空字符串
size_t
NB:很明显,如果你从这里抛出,或者返回一个
free

,你就不再是一个好的C函数。我可能会为逻辑创建一个纯 C++ 函数,然后是一个纯 C 函数,它调用 C++ 函数并处理它可能返回的任何错误,用 C 语言来说,例如像这样:

char*
    

© www.soinside.com 2019 - 2024. All rights reserved.