使用JNI从Java访问C++对象数组

4

根据我的另一个问题Some Name的回答,我想尝试相反的方式并改进之前的问题:

我想要执行以下步骤:

  1. 在Java中分配缓冲区
  2. 在C++中填充数组
  3. 编辑Java中的值
  4. 在C++中进行某些数字计算
  5. 读取Java中的值

考虑下面的C++类:

#pragma once
#pragma pack(push, 8)
class Point
{

public:
    double x;
    double y;
    Point();
    virtual ~Point();

    void ebeProduct(Point &other) {
        x = x * other.x;
        y = y * other.y;
    }
};



Point::Point()
{
}


Point::~Point()
{
}

#pragma pack(pop)

以下代码片段:
Point point = Point();
std::cout << "Size of Point: " << sizeof(Point) << std::endl;
std::cout << "Address is: " << &point << std::endl;
std::cout << "x address is: " << &(point.x) << " and offset is: " << (long long)&(point.x) - (long long)&point << std::endl;
std::cout << "y address is: " << &(point.y) << " and offset is: " << (long long)&(point.y) - (long long)&point << std::endl;

显示以下结果:
Size of Point: 24
Address is: 000000D45B0FF2C0
x address is: 000000D45B0FF2C8 and offset is: 8
y address is: 000000D45B0FF2D0 and offset is: 16

现在我可以在Java中假设以下大小:

final int NUMBER_OF_POINTS = 10;
final int SIZE_OF_POINT = 24;
final int X_OFFSET = 8;
final int Y_OFFSET = 16;

接下来,我将使用unsafe分配一个内存区域,并调用public static native void allocatePoints(long address, int numberOfPoints);方法,以便用Point对象填充它们:
long address = unsafe.allocateMemory(NUMBER_OF_POINTS * SIZE_OF_POINT);
allocatePoints(address, NUMBER_OF_POINTS);

填充点的C++代码:

Point * points = (Point *) address;
for (int pointIndex = 0; pointIndex < numberOfPoints; pointIndex++) {
    new(points + pointIndex) Point();
}

接下来,我将根据给定的偏移量,在Java中填充任意数字到点集中:
for (int i = 0; i < NUMBER_OF_POINTS; i++) {
  unsafe.putDouble(address + i * SIZE_OF_POINT + X_OFFSET, i);
  unsafe.putDouble(address + i * SIZE_OF_POINT + Y_OFFSET, i * 2);
}

接下来,调用 public static native void multiplyPoints(); 方法,它仅会将每个第二个点与其前一个点相乘。

最后,我将在Java中打印给定的值:

for (int i = 0; i < NUMBER_OF_POINTS; i++) {
  System.err.print(unsafe.getDouble(address + i * SIZE_OF_POINT + X_OFFSET));
  System.err.print(" ; ");
  System.err.println(unsafe.getDouble(address + i * SIZE_OF_POINT + Y_OFFSET));
}

这些阶段实际上输出了正确的结果。
我的问题是,这些阶段是否合理:我是否可以假设给定的偏移量是正确的(暂时在同一台机器上),并且直接将值写入Java缓冲区,并从C++对象中读取它们总是会产生正确的结果?
1个回答

1

我总体上看到三个潜在的陷阱:

  1. Point 类中的填充和虚函数表可能会破坏其大小:C++ 编译器可能会决定将 Point 类的成员按其所需的对齐方式对齐(例如,double 需要 8 字节对齐,而 char 没有指定任何对齐方式)。

    同样,如果您的 C++ 对象包含任何 virtual 方法(就像您的示例一样),则该对象将在前面具有虚函数表。在分配内存和访问单个成员时,您需要考虑这一点。

  2. Point 的对齐可能会破坏数组的步幅:假设您的类的最后一个元素有一个额外的 char,那么下一个元素将对齐到最接近 8 字节的倍数。 (换句话说,您的数组步幅将是 8+8+1(数据)+7 字节填充)

    在分配内存和访问单个成员时,您需要考虑这一点。

  3. sun.misc.Unsafe 正在退出历史舞台。此代码在 JDK11 及更高版本中可能最终停止工作。


1+2:关于对齐:我正在使用#pragma pack(16)指令,这应该保证一切都是对齐的。 3:如果Unsafe被弃用,并且没有其他方式来分配大块内存,我将使用ByteBuffer。 - Guy Yafe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接