javac++jnr

How to copy native memory to DirectByteBuffer


I know one way - using memcpy on C++ side:

C++ method:

void CopyData(void* buffer, int size)
{
    memcpy(buffer, source, size);
}

JNR mapping:

void CopyData(@Pinned @Out ByteBuffer byteBuffer, @Pinned @In int size);

Java invocation:

ByteBuffer buffer = ByteBuffer.allocateDirect(size);
adapter.CopyData(buffer, size);

But I would like to handle case when native code does not copy data, but only returns pointer to the memory which is to be copied:

C++ methods:

void* GetData1()
{
    return source;
}

// or

struct Data
{
    void* data;
};

void* GetData2(Data* outData)
{
    outData->data = source;
}

I know how to write JNR mapping to be able to copy data to HeapByteBuffer:

Pointer GetData1();

// or

void GetData2(@Pinned @Out Data outData);

final class Data extends Struct {

    public final Struct.Pointer data;

    public DecodeResult(Runtime runtime) {
        super(runtime);

        data = new Struct.Pointer();
    }
}

Java invocation:

ByteBuffer buffer = ByteBuffer.allocate(size);
Pointer dataPtr = adapter.GetData1();
dataPtr.get(0, buffer.array(), 0, buffer.array().length);

// or

ByteBuffer buffer = ByteBuffer.allocate(size);
Data outData = new Data(runtime);
adapter.GetData2(outData);

Pointer dataPtr = outData.data.get();
dataPtr.get(0, buffer.array(), 0, buffer.array().length);

But I have not found a way to copy memory to DirectByteBuffer instead of HeapByteBuffer. The above snippet of code does not work for DirectByteBuffer because buffer.array() is null for such a buffer, as it is backed by native memory area.

Please help.


Solution

  • I have found several ways to perform copying of JNR native memory to DirectByteBuffer. They differ in efficiency. Currently I use the following aproach, I don't know whether is it the best or intended by JNR authors:

    ByteBuffer buffer = ByteBuffer.allocateDirect(size);
    Pointer dataPtr = adapter.GetData1();
    long destAddress = ((DirectBuffer)buffer).address();
    Pointer destPtr = AsmRuntime.pointerValue(destAddress, runtime);
    
    assert dataPtr.isDirect() && destPtr.isDirect();
    
    dataPtr.transferTo(0, destPtr, 0, size);
    

    or

    ByteBuffer buffer = ByteBuffer.allocateDirect(size);
    Data outData = new Data(runtime);
    adapter.GetData2(outData);
    
    Pointer dataPtr = outData.data.get();
    long destAddress = ((DirectBuffer)buffer).address();
    Pointer destPtr = AsmRuntime.pointerValue(destAddress, runtime);
    
    assert dataPtr.isDirect() && destPtr.isDirect();
    
    dataPtr.transferTo(0, destPtr, 0, size);
    

    It is important that the assert clause above is fulfilled. It guarantees that pointers are jnr.ffi.provider.jffi.DirectMemoryIO instances, and the efficient memcpy method is used for copying (check implementation of DirectMemoryIO.transferTo()).

    The alternative is to wrap DirectByteBuffer using the following method:

    Pointer destPtr = Pointer.wrap(runtime, destAddress);
    

    or

    Pointer destPtr = Pointer.wrap(runtime, destAddress, size);
    

    but no:

    Pointer destPtr = Pointer.wrap(runtime, buffer);

    The first and second pointers are backed by DirectMemoryIO, but the third pointer is backed by ByteBufferMemoryIO and it involves slow byte-by-byte copying.

    The one drawback is that DirectMemoryIO instance is quite heavyweight. It allocates 32 bytes on JVM heap, so in case of plenty of JNR invocations, all DirectMemoryIO instances consume big part of memory.