Search code examples
javaarrayscallbackjna

Copy data from an unmanaged memory pointer to a managed unsigned byte array in Java


I have a c# code I am trying to implement in Java using JNA currently stuck in returning a preview image during my device scan process

C#

[DllImport("RS_SDK.dll",
          CharSet = CharSet.Ansi,
        EntryPoint = "RS_RegisterPreviewCallback")]
        public static extern int RS_RegisterPreviewCallback(int deviceHandle, RSPreviewDataCallback captureCallback); 


public delegate void RSRawPreviewCallback(int errorCode, byte[] imageData, int imageWidth, int imageHeight);


RSPreviewDataCallback previewCallback = new RSPreviewDataCallback(previewDataCallback);

private void previewDataCallback(int deviceHandle, int errorCode, IntPtr imageData, int imageWidth, int imageHeight)
        {
            log("previewDataCallback called....");
            if (imageData != null)
            {
                int prevImageWidth = imageWidth;
                int prevImageHeight = imageHeight;
                byte[] prevImageData = new byte[imageWidth * imageHeight];
                Marshal.Copy(imageData, prevImageData, 0, imageWidth * imageHeight);
                
                RSRawPreviewCallback callback = new RSRawPreviewCallback(previewCallbackInt);
                Invoke(callback, errorCode, prevImageData, prevImageWidth, prevImageHeight);
            }
            log("previewDataCallback done....");
        }

m_result = RS_RegisterPreviewCallback(deviceHandle,previewCallback);

My Java implementation so far

public interface RSPreviewDataCallback extends Callback {
        void RSPreviewDataCallback(int deviceHandle, int errorCode, Pointer imageData, int imageWidth, int imageHeight);
    }

public interface RS_SDK extends StdCallLibrary {
        RS_SDK INSTANCE = (RS_SDK)
                Native.load("RS_SDK_64", RS_SDK.class);

int RS_RegisterPreviewCallback(int deviceHandle, RSPreviewDataCallback captureCallback);
}

    @Override
    public void RSPreviewDataCallback(int deviceHandle, int errorCode, Pointer imageData, int imageWidth, int imageHeight) {

        byte[] dataByteArray = new byte[imageWidth * imageHeight];
        System.arraycopy( imageData, 0, dataByteArray, 0, imageWidth * imageHeight);
        try {
            String data = new String(dataByteArray, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }

and I run into this error:

java.lang.ArrayStoreException: arraycopy: source type com.sun.jna.Pointer is not an array
    at java.base/java.lang.System.arraycopy(Native Method)

I believe the imageData is a memory reference of the byte array and Marshal.Copy(imageData, prevImageData, 0, imageWidth * imageHeight); copies the location in memory to the bitmap array.

  1. I am not sure if my interface RSPreviewDataCallback signature is correct
  2. I don't know if there is a JNA equivalent to copy the memory location to a byte array

Solution

  • Regarding question 2, the JNA call to get a byte array from a Pointer is Pointer.getByteArray(). So you could grab the bytes from the pointer using imageData.getByteArray(0, imagewidth * imageheight).

    Note that Java does not have unsigned byte variables, so while the binary will be correct, you'll have to perform a conversion to a wider type to get the positive value. The bitmask b & 0xff will widen to an int for you and restore the "unsigned" value.

    Regarding question 1, the mapping will work if you use the answer to question2. In most JNA function mappings of arrays, you can simplify this by putting a byte[] array as the argument. However, JNA requires specifically mapped types for callbacks, so primitive arrays aren't included, so the Pointer method is preferred.