javaarraysjnaprimitive-typesclp

How to pass java primitive arrays to a C dll using 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
}

Solution

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