简体   繁体   English

JNA 结构可以支持不变性吗?

[英]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).使用 JNA 结构时的一个小麻烦是它们是可变的(我的意思是完全可变的),因为默认情况下所有字段都必须是公共的并且不能是final的(尽管见下文)。 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.这意味着如果我们想将 JNA 结构公开为 DTO(数据传输对象),客户端基本上可以处理数据。

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).这里的VkPhysicalDeviceLimits是从本地库中检索到的 JNA 结构(为清楚起见,省略了线程同步)。 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.如果该结构仅包含几个字段,那么我们显然会创建一个包装器,该包装器可以正确封装底层 JNA 数据,以执行所需的任何防御性复制。 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.当然,我们可以在每次调用limits()访问器时简单地检索结构的实例,但通常我们希望避免对本机层的不必要调用,理想情况下数据应该在本地缓存。

Couple of other notes:其他几点注意事项:

  • The structure is code-generated from the native C header file but could be re-generated if changes were needed.该结构是从本机 C header 文件代码生成的,但如果需要更改,可以重新生成。

  • The data does not change over the lifetime of the application, ie it's basically static data.数据在应用程序的生命周期内不会改变,即它基本上是 static 数据。

  • There are several other (equally large) structures in this API.这个 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?我看不到使用复制构造函数或其他方式“克隆”JNA 结构(例如)的任何方式? We could do a field-by-field copy using reflection but that feels dirty?我们可以使用反射进行逐个字段的复制,但这感觉很脏?

Can the fields be made final ?字段可以final吗? This is mentioned as 'risky' in some of the JNA documentation but I'm unsure why.这在一些 JNA 文档中被称为“有风险”,但我不确定为什么。

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. 根据 JNA API ,除了 JNA 的read()方法从本机 memory 填充该结构外, final修饰符将使字段只读。

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. final JNA 将通过read()覆盖该字段,否则该字段不可从 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.使用此选项时要小心,因为编译器通常会假定对字段的所有访问(对于给定的 Structure 实例)具有相同的值。 This modifier is invalid to use on J2ME.此修饰符在 J2ME 上无效。

The caution regards whether the field can change value;注意字段是否可以更改值; it can if the underlying memory changes and it is re-read.如果底层 memory 发生变化并重新读取,则可以。 In theory, a user could use this to bypass the read-only nature by:理论上,用户可以通过以下方式绕过只读性质:

  • Calling the structure's getPointer() value调用结构的getPointer()
  • Directly writing to native memory at the appropriate offset在适当的偏移量处直接写入本机 memory
  • Calling the structure's read() method.调用结构的read()方法。

Is this immutable?这是不可变的吗? Probably not, but it's no different than the existing non-modular limitations where reflective access can bypass the final modifier.可能不会,但这与反射访问可以绕过final修饰符的现有非模块化限制没有什么不同。 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() ;一种开销很小的可能想法是将Structure子类化为类似于ImmutableStructure的东西,您可以在其中覆盖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.最初它允许将值读取到final字段中,但是您可以在将 boolean 返回给用户之前更改它,从而将read()变为无操作。 This still allows reflective access (like any Java class) but adds a further step preventing unintentional modification.这仍然允许反射访问(如任何 Java 类),但增加了防止无意修改的进一步步骤。

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.如果您不想 go 这条路线并想要一个真正的防御性副本,则您不得不放弃结构 API 和 go 的便利性,该结构直接从数组/字节缓冲区的本机方法返回并处理. 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.您可以通过计算字段顺序、获取字段的偏移量(甚至将它们缓存在 map 中供以后使用)以及使用只读 getter 直接读取您关心的偏移量处的字节值来执行类似于Structure class 的操作。

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) .通过反射的逐字段复制(或从本机读取)发生在结构的指针构造函数的底层,因此创建防御性副本可以像读取结构大小的字节数组一样简单,创建一个新的Memory object 并写入将这些字节传递给它,并将指向该Memory的指针传递给新副本的构造函数以通过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.您也可以考虑简单地序列化 object 并将其反序列化为新的 object。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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