英文:
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中,我有rowLower
,rowUpper
,rowStarts
,columnscolumns
和elements
作为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< 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) {
<... some magic to compute the position...>
Pounter<Double> 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];
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论