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

时间:2019-02-25 13:58:58

标签: java c++ java-native-interface

在我的previous questionSome Name的回应之后,我想尝试相反的方法并增强前面的问题:

我想执行以下步骤:

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

请考虑以下C ++类:

import itertools
dic = (set(itertools.product([-1, 0, 1], [0, 1, 2, 3, 4])))
for key,values in enumerate(dic):
    address = np.where(a==values)[0]
    a[np.delete(address,np.unique(address,return_index=True)[1])]=key
one_dim_result = a[:,0]

以下代码段:

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

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

Size of Point: 24
Address is: 000000D45B0FF2C0
x address is: 000000D45B0FF2C8 and offset is: 8
y address is: 000000D45B0FF2D0 and offset is: 16

接下来,我将使用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

填补问题的C ++代码:

long address = unsafe.allocateMemory(NUMBER_OF_POINTS * SIZE_OF_POINT);
allocatePoints(address, NUMBER_OF_POINTS);

接下来,我将根据给定的偏移量用任意数字填充Java中的点:

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

接下来,调用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); } ,它只会使第二点与前面的点相乘。

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

public static native void multiplyPoints();

这些阶段实际上会打印正确的结果。
我的问题是这些阶段是否合理:我是否可以假设给定的偏移量是正确的(目前在同一台计算机上),并且将值直接写入Java中的缓冲区,并从C ++中的对象中读取它们,将始终产生正确的结果?

1 个答案:

答案 0 :(得分:1)

我通常会看到三个潜在的陷阱:

  1. Point中的填充和vtable可能会弄乱大小:C ++编译器可能会决定将Point的成员对齐到所需的对齐方式(例如double要求8字节对齐,而char不指定任何对齐。)

    同样,如果您的C ++对象包含任何virtual方法(如您的示例所示),则您的 对象将在前面有一个vtable。你需要考虑这个 分配内存和访问单个成员时的帐户。

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

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

  3. sun.misc.Unsafe即将淘汰。此代码最终可能会在JDK11及更高版本中停止工作。