JNA 结构可以支持不变性吗?
Can a JNA structure support immutability?
使用 JNA 结构时的一个小烦恼是它们是可变的(我的意思是 完全 可变)因为默认情况下所有字段 必须 是 public 并且不能是 final
(尽管见下文)。这意味着如果我们想将 JNA 结构公开为 DTO(数据传输对象),客户端基本上可以随意处理数据。
考虑这个简单的例子:
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!
}
}
这里的 VkPhysicalDeviceLimits
是从本机库中检索到的 JNA 结构(为清楚起见,省略了线程同步)。访问器是惰性的,但同样的问题适用于它是惰性的、显式创建为成员、由框架注入,还是其他。
如果该结构仅包含少量字段,那么我们显然会创建一个包装器来正确封装执行任何防御性复制所需的底层 JNA 数据。不幸的是,这个 structure 有一百多个字段,所有这些字段都必须是 public 并且是可变的 - 手动编写包装器既乏味又容易出错。
当然,我们可以在每次调用 limits()
访问器时简单地检索结构的实例,但通常我们希望避免对本机层进行不必要的调用,理想情况下,数据应缓存在本地。
一些其他注释:
该结构是从本机 C 头文件代码生成的,但如果需要更改可以重新生成。
数据在应用程序的生命周期内不会改变,即它基本上是静态数据。
这个API中还有其他几个(同样大的)结构。
那么如何封装这些数据呢?
我看不到 'cloning' JNA 结构(例如)使用复制构造函数或其他方式的任何方式?我们可以使用反射逐字段复制,但感觉很脏?
字段可以final
吗?这在一些 JNA 文档中被称为 'risky',但我不确定为什么。
有什么建议吗?
Per the JNA API,final
修饰符将使字段成为只读的,除了 JNA 的 read()
方法从本机内存填充该结构。
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.
注意字段是否可以改变值;如果底层内存发生变化并被重新读取,它可以。理论上,用户可以通过以下方式使用它来绕过只读性质:
- 调用结构的
getPointer()
值
- 以适当的偏移量直接写入本机内存
- 正在调用结构的
read()
方法。
这是不可变的吗?可能不是,但这与反射访问可以绕过 final
修饰符的现有非模块化限制没有什么不同。它至少应该防止“不小心”修改值,我认为这是不变性的意图。
一个开销很小的可能想法是 class 将 Structure
替换为类似 ImmutableStructure
的东西,您可以在其中覆盖 read()
;最初它允许将值读入 final
字段,但随后您可以在将其返回给用户之前更改布尔值,将 read()
变为空操作。这仍然允许反射访问(像任何 Java class)但增加了防止无意修改的进一步步骤。
如果你不想走那条路,想要一个真正的防御副本,你有点不得不放弃结构API的便利,去低级直接处理array/buffer 从本机方法返回的字节数。您可以通过计算字段顺序、获取字段的偏移量(甚至将它们缓存在地图中供以后使用)以及直接读取您关心的偏移量处的字节值来执行类似于 Structure
class 的操作只读吸气剂。
通过反射逐字段复制(或从本机读取)发生在结构的指针构造函数的幕后,因此创建防御副本可以像读取结构大小的字节数组一样简单,创建一个新 Memory
对象并将这些字节写入其中,并将指向 Memory
的指针传递给新副本的构造函数以通过 super(p)
使用。可能有更有效的方法来做到这一点。
您也可以考虑简单地序列化对象并将其反序列化为新对象。
使用 JNA 结构时的一个小烦恼是它们是可变的(我的意思是 完全 可变)因为默认情况下所有字段 必须 是 public 并且不能是 final
(尽管见下文)。这意味着如果我们想将 JNA 结构公开为 DTO(数据传输对象),客户端基本上可以随意处理数据。
考虑这个简单的例子:
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!
}
}
这里的 VkPhysicalDeviceLimits
是从本机库中检索到的 JNA 结构(为清楚起见,省略了线程同步)。访问器是惰性的,但同样的问题适用于它是惰性的、显式创建为成员、由框架注入,还是其他。
如果该结构仅包含少量字段,那么我们显然会创建一个包装器来正确封装执行任何防御性复制所需的底层 JNA 数据。不幸的是,这个 structure 有一百多个字段,所有这些字段都必须是 public 并且是可变的 - 手动编写包装器既乏味又容易出错。
当然,我们可以在每次调用 limits()
访问器时简单地检索结构的实例,但通常我们希望避免对本机层进行不必要的调用,理想情况下,数据应缓存在本地。
一些其他注释:
该结构是从本机 C 头文件代码生成的,但如果需要更改可以重新生成。
数据在应用程序的生命周期内不会改变,即它基本上是静态数据。
这个API中还有其他几个(同样大的)结构。
那么如何封装这些数据呢?
我看不到 'cloning' JNA 结构(例如)使用复制构造函数或其他方式的任何方式?我们可以使用反射逐字段复制,但感觉很脏?
字段可以final
吗?这在一些 JNA 文档中被称为 'risky',但我不确定为什么。
有什么建议吗?
Per the JNA API,final
修饰符将使字段成为只读的,除了 JNA 的 read()
方法从本机内存填充该结构。
Structure fields may additionally have the following modifiers:
final
JNA will overwrite the field viaread()
, 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.
注意字段是否可以改变值;如果底层内存发生变化并被重新读取,它可以。理论上,用户可以通过以下方式使用它来绕过只读性质:
- 调用结构的
getPointer()
值 - 以适当的偏移量直接写入本机内存
- 正在调用结构的
read()
方法。
这是不可变的吗?可能不是,但这与反射访问可以绕过 final
修饰符的现有非模块化限制没有什么不同。它至少应该防止“不小心”修改值,我认为这是不变性的意图。
一个开销很小的可能想法是 class 将 Structure
替换为类似 ImmutableStructure
的东西,您可以在其中覆盖 read()
;最初它允许将值读入 final
字段,但随后您可以在将其返回给用户之前更改布尔值,将 read()
变为空操作。这仍然允许反射访问(像任何 Java class)但增加了防止无意修改的进一步步骤。
如果你不想走那条路,想要一个真正的防御副本,你有点不得不放弃结构API的便利,去低级直接处理array/buffer 从本机方法返回的字节数。您可以通过计算字段顺序、获取字段的偏移量(甚至将它们缓存在地图中供以后使用)以及直接读取您关心的偏移量处的字节值来执行类似于 Structure
class 的操作只读吸气剂。
通过反射逐字段复制(或从本机读取)发生在结构的指针构造函数的幕后,因此创建防御副本可以像读取结构大小的字节数组一样简单,创建一个新 Memory
对象并将这些字节写入其中,并将指向 Memory
的指针传递给新副本的构造函数以通过 super(p)
使用。可能有更有效的方法来做到这一点。
您也可以考虑简单地序列化对象并将其反序列化为新对象。