Make writing a habit together! This is the 9th day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

Introduction to the

We know that in native code there are many Pointers that are mapped as Pointers in JNA. In addition to Pointer, JNA provides more powerful Memory classes, and this article will explore the use of Pointer and Memory in JNA.

Pointer

Pointer is a class introduced in JNA to represent Pointers in native methods. Remember what Pointers are in native methods?

A pointer in a native method is actually an address, which is the memory address of the real object. So we define a peer property in Pointer that stores the memory address of the real object:

protected long peer;
Copy the code

In real time, the Pointer constructor needs to pass in the peer argument:

public Pointer(long peer) {
        this.peer = peer;
    }
Copy the code

Let’s see how we can get a real object from a Pointer, using a byte array as an example:

    public void read(long offset, byte[] buf, int index, int length) {
        Native.read(this, this.peer, offset, buf, index, length);
    }
Copy the code

This method actually calls the Native. Read method, so let’s look at the read method:

static native void read(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length);
Copy the code

You can see that it’s a real native method for reading a pointer object.

Pointer provides many other types of read methods in addition to Byte arrays.

Int * * a; / / a Pointer writes data to a Pointer:

    public void write(long offset, byte[] buf, int index, int length) {
        Native.write(this, this.peer, offset, buf, index, length);
    }
Copy the code

Again, the Native. Write method is called to write data.

Here the Native. Write method is also a Native method:

static native void write(Pointer pointer, long baseaddr, long offset, byte[] buf, int index, int length);
Copy the code

Pointer also provides methods for writing many other types of data.

Of course, there are more direct get* methods:

public byte getByte(long offset) {
        return Native.getByte(this, this.peer, offset);
    }
Copy the code

Special Pointer: Opaque

In Pointer, there are also two createConstant methods that create a Pointer that cannot be read or written:

    public static final Pointer createConstant(long peer) {
        return new Opaque(peer);
    }

    public static final Pointer createConstant(int peer) {
        return new Opaque((long)peer & 0xFFFFFFFF);
    }
Copy the code

But actually returned Opaque class, this class inherits from Pointer, but it’s all the read or write method, will throw an UnsupportedOperationException:

    private static class Opaque extends Pointer {
        private Opaque(long peer) { super(peer); }
        @Override
        public Pointer share(long offset, long size) {
            throw new UnsupportedOperationException(MSG);
        }
Copy the code

Memory

Pointer is a basic Pointer mapping. For memory allocated using native Malloc methods, we need to know the size of the Pointer allocated in addition to the starting position. So a simple Pointer is not enough.

In this case, we need to use Memory.

Memory is a special Pointer that holds the size of the allocated space. Let’s take a look at the definition of Memory and the attributes it contains:

public class Memory extends Pointer {
...
    private static ReferenceQueue<Memory> QUEUE = new ReferenceQueue<Memory>();
    private static LinkedReference HEAD; // the head of the doubly linked list used for instance tracking
    private static final WeakMemoryHolder buffers = new WeakMemoryHolder();
    private final LinkedReference reference; // used to track the instance
    protected long size; // Size of the malloc'ed space
...
}
Copy the code

There are five data defined in Memory, and we’ll go through each of them.

Size is the size of the Memory. The size of Memory is the size of the Memory.

    public Memory(long size) {
        this.size = size;
        if (size <= 0) {
            throw new IllegalArgumentException("Allocation size must be greater than zero");
        }
        peer = malloc(size);
        if (peer == 0)
            throw new OutOfMemoryError("Cannot allocate " + size + " bytes");

        reference = LinkedReference.track(this);
    }
Copy the code

As you can see, data of type Memory needs to be passed a size parameter to indicate how much Memory is occupied. Of course, this size has to be greater than 0.

Then call the Malloc method of the native method to allocate a memory space, and return the peer to save the starting address of the memory space. If the peer is ==0, the allocation fails.

If the allocation is successful, the current Memory is saved to a LinkedReference to track the current location.

We can see that Memory has two linkedin ferences, one HEAD and one reference.

The LinkedReference itself is a WeakReference. The object referenced by the weekReference is reclaimed as long as garbage collection is performed, regardless of whether there is insufficient memory.

private static class LinkedReference extends WeakReference<Memory>
Copy the code

Let’s look at the constructor for LinkedReference:

private LinkedReference(Memory referent) {
            super(referent, QUEUE);
        }
Copy the code

This QUEUE is the ReferenceQueue, which represents the list of objects to be reclaimed by the GC.

In addition to setting size, the Memory constructor calls:

reference = LinkedReference.track(this);
Copy the code

Take a closer look at the LinkedReference. Track method:

static LinkedReference track(Memory instance) { // use a different lock here to allow the finialzier to unlink elements too synchronized (QUEUE) { LinkedReference stale; // handle stale references here to avoid GC overheating when memory is limited while ((stale = (LinkedReference) QUEUE.poll()) ! = null) { stale.unlink(); } } // keep object allocation outside the syncronized block LinkedReference entry = new LinkedReference(instance); synchronized (LinkedReference.class) { if (HEAD ! = null) { entry.next = HEAD; HEAD = HEAD.prev = entry; } else { HEAD = entry; } } return entry; }Copy the code

This method takes Memory objects from the QUEUE that are going to be garbage collected and unlinks them from your LinkedReference. Finally, add the newly created object to your linkedin ference.

Because queues and heads in Memory are class variables, this ference holds all Memory objects in the JVM.

Finally, the corresponding read and write methods are provided in Memory, but the Pointer method is different from the Pointer method. The boundsCheck method is used as follows:

    public void read(long bOff, byte[] buf, int index, int length) {
        boundsCheck(bOff, length * 1L);
        super.read(bOff, buf, index, length);
    }

    public void write(long bOff, byte[] buf, int index, int length) {
        boundsCheck(bOff, length * 1L);
        super.write(bOff, buf, index, length);
    }
Copy the code

Why boundsCheck? This is because unlike Pointer, Memory has a size property that stores the size of allocated Memory. BoundsCheck is used to determine if an address is out of bounds to ensure program security.

conclusion

Pointer and Memory are advanced JNA features that you should consider if you want to map to native alloc methods.

This article is available at www.flydean.com/06-jna-memo…

The most popular interpretation, the most profound dry goods, the most concise tutorial, many tips you didn’t know waiting for you to discover!

Welcome to pay attention to my public number: “procedures those things”, understand technology, more understand you!