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
}
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 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))