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

280 views Asked by At

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

There are 2 answers

0
simon On BEST ANSWER

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))
0
Daniel Widdis On

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