简体   繁体   中英

Can a JNA structure support immutability?

A minor irritation when using JNA structures is that they are mutable (and I mean totally mutable) since by default all fields must be public and cannot be final (though see below). This means that if we would like to expose a JNA structure as a DTO (data transfer object) the client can basically monkey with the data.

Consider this trivial example:

class PhysicalDevice {
    private VkPhysicalDeviceLimits limits;

    public VkPhysicalDeviceLimits limits() {
        if(limits == null) {
            limits = ...    // Instantiate from API using JNA
        }
        return limits;      // <-- Anyone can fiddle with this fully mutable data!
    }
}

Here the VkPhysicalDeviceLimits is a JNA structure retrieved from the native library (thread synchronization omitted for clarity). The accessor is lazy but the same issue applies whether it's lazy, created as a member explicitly, injected by a framework, or whatever.

If the structure consisted of just a handful of fields then we would obviously create a wrapper that properly encapsulated the underlying JNA data performing any defensive copying required. Unfortunately this structure has over a hundred fields, all of which have to be public and mutable - writing a wrapper by hand would be tedious and error prone.

Of course we could simply retrieve an instance of the structure on each invocation of the limits() accessor but in general we want to avoid unnecessary calls to the native layer and ideally the data should be cached locally.

Couple of other notes:

  • The structure is code-generated from the native C header file but could be re-generated if changes were needed.

  • The data does not change over the lifetime of the application, ie it's basically static data.

  • There are several other (equally large) structures in this API.

So how to encapsulate this data?

I can't see any way of 'cloning' a JNA structure (for example) using a copy constructor or other means? We could do a field-by-field copy using reflection but that feels dirty?

Can the fields be made final ? This is mentioned as 'risky' in some of the JNA documentation but I'm unsure why.

Any suggestions?

Per the JNA API , the final modifier will make a field read-only with the exception of JNA's read() method to populate that structure from native memory.

Structure fields may additionally have the following modifiers:

final JNA will overwrite the field via read() , but otherwise the field is not modifiable from Java. Take care when using this option, since the compiler will usually assume all accesses to the field (for a given Structure instance) have the same value. This modifier is invalid to use on J2ME.

The caution regards whether the field can change value; it can if the underlying memory changes and it is re-read. In theory, a user could use this to bypass the read-only nature by:

  • Calling the structure's getPointer() value
  • Directly writing to native memory at the appropriate offset
  • Calling the structure's read() method.

Is this immutable? Probably not, but it's no different than the existing non-modular limitations where reflective access can bypass the final modifier. It should at least prevent "accidentally" modifying the value, which I think is the intent of immutability.

One possible thought with very little overhead would be subclassing Structure with something like an ImmutableStructure where you override read() ; initially it allows reading values into final fields but then you can change a boolean before returning it to the user, turning read() into a no-op. This still allows reflective access (like any Java class) but adds a further step preventing unintentional modification.

If you don't want to go that route and want a true defensive copy, you somewhat have to give up the convenience of the Structure API and go low-level and deal directly with the array/buffer of bytes that are returned from native methods. You can do things similar to the Structure class by calculating field orders, getting offsets of fields (even caching them in a map for later use), and directly reading the byte values at offsets you care about with read-only getters.

Field-by-field copy (or reading from native) via reflection happens under the hood in the Structure's pointer constructor, so creating a defensive copy could be as simple as reading a byte array of the structure's size, creating a new Memory object and writing those bytes to it, and passing the pointer to that Memory to the new copy's constructor to use via super(p) . There are probably more efficient ways to do that.

You might also consider simply serializing the object and deserializing it into a new object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM