如何使用JNA将Java原始数组传递给C DLL?

huangapple go评论81阅读模式
英文:

How to pass java primitive arrays to a C dll using JNA?

问题

在使用JNA将Java代码映射到DLL时,当C函数的参数是指针时,应该如何传递/使用Java数组(int[]double[])?我遇到了一个错误,我认为错误可能在我将数组映射到C函数时出现了。


我尝试过的方法以及我正在尝试修复的错误

在我的项目中,我需要重构clp-java背后的代码库。在这样做时,我有一个带有以下函数的C头文件,该函数为LP问题添加约束(例如:2.25*x1 - 3.3*x2 =4)。

CLPLIB_EXPORT void CLP_LINKAGE Clp_addRows(Clp_Simplex *model, int number,
    const double *rowLower, const double *rowUpper,
    const CoinBigIndex *rowStarts, const int *columns,
    const double *elements);

在Java中,我有rowLowerrowUpperrowStartscolumnscolumnselements作为Java数组(int[]double[])。clp-java使用BridJ,其中上面的函数通过以下方式调用:

CLPNative.clpAddRows(pointerToModel, number, 
        Pointer.pointerToDoubles(rowLower), 
        Pointer.pointerToDoubles(rowUpper), 
        Pointer.pointerToInts(rowStarts), 
        Pointer.pointerToInts(columnscolumns), 
        Pointer.pointerToDoubles(elements));

使用普通的JNA,JNA文档指出数组映射到指针,因此调用C函数的方式如下:

CLPNative.clpAddRows(pointerToModel, number, 
        rowLower, rowUpper, rowStarts, columnscolumns, elements);

不幸的是,当我将相同的数组传递给两种方法并在内存中检索数据时,我得到了约束中第二个变量的不同答案(第一个变量是正常的):BridJ得到-3.3,我的JNA方法输出1.777E-307。同样的DLL,同样的机器(Java 11)。

在互联网上,我找到了这个示例,它将Java中的数组映射到JNA指针,并将此指针传递给C函数。我尝试了以下方法:

private Pointer intArrayToPointer(int[] pArray) {
    Pointer pointerToArray = new Memory(pArray.length*Native.getNativeSize(Integer.TYPE));
    for (int i=0; i<pArray.length; i++) {
      pointerToArray.setInt(i, pArray[i]);
    }
    return pointerToArray;
}

尽管我在我的JNA函数调用中使用了这个方法(并相应地更改了JNA接口),但我得到了Java错误“invalid memory access”。根据这个StackOverflow问答setInt()中的偏移量需要通过Native.getNativeSize(Integer.TYPE)进行偏移),修复后仍然得到了相同的错误输出(x2的系数为1.777E-307,而不是-3.3):

CLPNative.clpAddRows(pointerToModel, number, 
        doubleArrayToPointer(rowLower),....);

附加信息:我使用以下函数/方法检查系数:

public double getNativeConstraintCoefficient(CLPConstraint pConstraint, CLPVariable pVariable) {
    // ...一些计算位置的魔法...
    Pointer<Double> elements = CLPNative.Clp_GetElements(pointerToModel);
    return elements.getDoubleAtIndex(pos-1); // 使用BridJ
    return elements.getDouble(pos - 1);  // 使用JNA
}
英文:

When mapping java code to a DLL using JNA, how should one pass/use a java array (int[], double[]) in a C call when the C's function argument is a pointer? I am facing a bug, and I think it is somewhere in my mapping of arrays to C.


What I tried: and the bug that I am trying to fix

For my project I need to restructure the codebase behind clp-java. In doing so, I have a C header file with the following function which add constraints for a LP problem ( for example: 2.25*x1 - 3.3*x2 =4).

CLPLIB_EXPORT void CLP_LINKAGE Clp_addRows(Clp_Simplex *model, int number,
const double *rowLower, const double *rowUpper,
const CoinBigIndex *rowStarts, const int *columns,
const double *elements);

In java I have rowLower, rowUpper, rowStarts, columnscolumns and elements as java arrays (either int[] of double[]). clp-java uses BridJ, where the function above is called via

CLPNative.clpAddRows(pointerToModel, number, 
		Pointer.pointerToDoubles(rowLower), 
		Pointer.pointerToDoubles(rowUpper), 
		Pointer.pointerToInts(rowStarts), 
		Pointer.pointerToInts(columnscolumns), 
		Pointer.pointerToDoubles(elements));

Using plain JNA, the JNA documentation states that arrays map to pointers, such that a call to the C function would be:

CLPNative.clpAddRows(pointerToModel, number, 
		rowLower, rowUpper, rowStarts, columnscolumns, elements);

Unfortunately, when I pass the same arrays to both methods and retrieve the data in memory, I get different answers for the second variable in the constraint (first one is ok): BridJ yields -3.3, my JNA method outputs 1.777E-307. Same DLL, same machine (Java 11).

On the internet I found this example, which maps an array in Java to a JNA pointer and passes this pointer to the C function. This I tried using:

private Pointer intArrayToPointer(int[] pArray) {
    Pointer pointerToArray = new Memory(pArray.length*Native.getNativeSize(Integer.TYPE));
    for (int i=0; i&lt; pArray.length; i++) {
      pointerToArray.setInt(i, pArray[i]);
    }
    return pointerToArray;
  }

Though if I use this in my JNA function call (and change the JNA interface accordingly), I get a Java error "invalid memory access". Fixing this based on this StackOverflow Q/A (the offset in setInt() needs to be shifted by Native.getNativeSize(Integer.TYPE), reveals the same erroneous output (coefficient for x2 is 1.777E-307 instead of -3.3)

CLPNative.clpAddRows(pointerToModel, number, 
		doubleArrayToPointer(rowLower),....);

Extra info: I check the coefficients with the following function/method:

  public double getNativeConstraintCoefficient(CLPConstraint pConstraint, CLPVariable pVariable) {
	&lt;... some magic to compute the position...&gt;
     Pounter&lt;Double&gt; elements = CLPNative.Clp_GetElements(pointerToModel);
     return elements.getDoubleAtIndex(pos-1); // using BridJ
     return elements.getDouble(pos - 1)  // using JNA
}

答案1

得分: 0

找到了,这是我读取数据的方式,而不是写入数据的方式。

BridJ 是一个高级封装,它确保你按照与双精度浮点数相对应的索引读取指针<double>(在我的机器上是8位)。换句话说:索引=0 读取位 1-8,索引=2 读取位 9-16。

JNA 就没有那么“高级”了。Pointer.getDouble(offset) 从其指向的地址开始读取双精度浮点数(8位值)。offset=0 读取位 1-8,offset=1 读取位 2-9。为了防止这种情况,需要将偏移量乘以你要查找的数据类型大小。

Pointer.getDouble(offset*Native.getNativeSize(Double.TYPE))

英文:

Found it, it is the way I read the data, not the way I write it.

BridJ is a fancy wrapper, which makes sure you read a pointer<double> at indices that correspond to doubles (on my machine that is 8 bits). In other words: index=0 reads bits 1-8 bits, index=2 reads bits 9-16.

JNA is not 'so fancy'. Pointer.getDouble(offset) reads a double (8-bit value) starting from the address it points to. offset=0 reads bits 1-8, and offset=1 reads 2-9. To prevent this, one needs to multiply the offset by the datatype you are looking for

Pointer.getDouble(offset*Native.getNativeSize(Double.TYPE))

答案2

得分: 0

你的回答中,你指出BridJ索引与数组对应,以便每个索引都是一个新的double,并且正确地指出如果你只从JNA指针中获取一个double,你需要使用byte偏移量,表明那不是“很高级”的。

然而,如果你希望,JNA也可以变得“高级”。只需使用以下代码:

Pointer.getDoubleArray(offset, arraySize);

例如:

int arraySize = pArray.length; // 根据你的代码我认为是这样的
double[] foo = elements.getDoubleArray(0, arraySize);
return foo[pos - 1];
英文:

In your answer you note that the BridJ indices correspond to the array, such that each index is a new double, and correctly note that if you're only fetching a single double from a JNA pointer, you need to use a byte offset, indicating that's "not as fancy".

However, JNA can be just "fancy" if you want it to be. Simply use this:

Pointer.getDoubleArray(offset, arraySize);

For example:

int arraySize = pArray.length; // I think, based on your code
double[] foo = elements.getDoubleArray(0, arraySize);
return foo[pos - 1];

huangapple
  • 本文由 发表于 2020年10月15日 03:01:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/64359913.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定