如何通过JNA访问包含std::vector<char*>的C++结构体?
这个问题挺典型的——JNA没法直接映射C++的std::vector,因为它不是简单的连续内存数组,而是一个包含内部指针、大小、容量的复杂容器。你Java里直接用String[]完全对应不上它的内存布局,所以才会无效。下面给你两种可行的解决方案:
为什么原写法无效?
先简单说下根源:std::vector<char*>在内存里不是一串连续的字符串指针,它的底层结构通常是三个指针(不同编译器可能略有差异):
- 指向数组起始的指针
- 指向已使用元素末尾的指针
- 指向容器容量末尾的指针
而Java的String[]是直接对应C风格的连续指针数组,两者内存结构完全不匹配,JNA自然没法正确解析。
解决方案1:修改C++库(推荐)
如果能修改你的C++库代码,这是最稳定可靠的方式,有两种思路:
思路A:改用固定大小的C风格数组
如果names的最大长度是可预期的,直接把std::vector换成固定大小的指针数组:
#include <cstdint> #define MAX_NAME_COUNT 100 // 根据实际情况调整 struct topic { char* src_id; char* dest_id; int32_t num; char* names[MAX_NAME_COUNT]; };
对应的Java结构体就可以直接用String[]了:
public final class Topic extends Structure { public String src_id; public String dest_id; public int num; public String[] names = new String[100]; // 和C++的MAX_NAME_COUNT对应 public Topic() { } @Override protected List<String> getFieldOrder() { return Arrays.asList("src_id", "dest_id", "num", "names"); } }
思路B:添加C风格的辅助函数
如果names的长度不确定,没法用固定数组,可以给C++库暴露两个辅助函数,用来获取vector的大小和单个元素:
#include <vector> #include <cstdint> struct topic { char* src_id; char* dest_id; int32_t num; std::vector<char*> names; }; // 注意要extern "C"避免C++名字 mangling extern "C" { // 获取names的元素数量 int32_t topic_get_names_size(struct topic* t) { return static_cast<int32_t>(t->names.size()); } // 获取第index个name(index从0开始) char* topic_get_name(struct topic* t, int32_t index) { if (index >=0 && index < static_cast<int32_t>(t->names.size())) { return t->names[index]; } return nullptr; } }
然后Java端先获取Topic结构体,再调用这些辅助函数来读取names:
public final class Topic extends Structure { public String src_id; public String dest_id; public int num; // 这里不再直接映射names,而是通过辅助函数获取 public Topic() { } @Override protected List<String> getFieldOrder() { return Arrays.asList("src_id", "dest_id", "num"); } } // 定义JNA接口调用辅助函数 public interface MyCLibrary extends Library { MyCLibrary INSTANCE = Native.load("myLibraryName", MyCLibrary.class); int topic_get_names_size(Topic t); String topic_get_name(Topic t, int index); } // 使用示例 Topic topic = ...; // 从C++库获取结构体 int nameCount = MyCLibrary.INSTANCE.topic_get_names_size(topic); String[] names = new String[nameCount]; for (int i = 0; i < nameCount; i++) { names[i] = MyCLibrary.INSTANCE.topic_get_name(topic, i); }
解决方案2:手动映射std::vector的内存布局(无修改C++库时用)
如果完全没法修改C++库,只能硬着头皮解析std::vector的内存结构。注意:这个方案依赖编译器的std::vector实现(比如GCC/Clang的布局是三个指针,MSVC可能不同),跨编译器可能失效。
首先在Java里定义一个对应std::vector<char*>的结构体:
public class VectorCharPointer extends Structure { public Pointer start; // 指向第一个元素的指针 public Pointer finish; // 指向最后一个已用元素的下一个位置 public Pointer endOfStorage; // 指向容器容量的末尾 @Override protected List<String> getFieldOrder() { return Arrays.asList("start", "finish", "endOfStorage"); } // 计算vector的元素数量 public int size() { // 两个指针的差值除以单个指针的大小,得到元素个数 return (int) ((finish.nativeValue() - start.nativeValue()) / Pointer.SIZE); } // 把vector里的char*转换成Java的String数组 public String[] getElements() { int size = size(); String[] elements = new String[size]; for (int i = 0; i < size; i++) { // 每次偏移i个指针大小,取出对应的char*指针,再转成String Pointer elemPtr = start.getPointer(i * Pointer.SIZE); elements[i] = elemPtr.getString(0); } return elements; } }
然后修改原来的Topic结构体,把String[] names换成VectorCharPointer names:
public final class Topic extends Structure { public String src_id; public String dest_id; public int num; public VectorCharPointer names; public Topic() { } @Override protected List<String> getFieldOrder() { return Arrays.asList("src_id", "dest_id", "num", "names"); } // 方便直接获取names数组 public String[] getNames() { return names.getElements(); } }
使用的时候,直接调用topic.getNames()就能得到对应的字符串数组了。
内容的提问来源于stack exchange,提问作者Fernando Lozano

