Squeak FFI中联合类型定义方法及对接含联合类型库的疑问
确实,Squeak FFI目前没有原生支持C语言的联合类型(union),不过我们可以通过内存布局等价映射的方式来解决这个问题,刚好适配你提到的H5L_info_t结构体场景。下面是具体的实现步骤和思路:
联合的本质是多个字段共享同一块内存区域,它的大小等于其中最大成员的字节数。所以我们可以在Squeak的FFI结构体定义中,用一个与联合最大成员尺寸相同的字段来占据这块内存,之后根据结构体的type字段来决定如何解析这个字段的值。
首先,先把结构体中除了联合之外的字段和相关枚举类型映射好:
枚举类型定义
先定义H5L_type_t和H5T_cset_t这两个枚举(注意要和HDF5头文件中的实际值对应,不同版本可能略有差异):
H5L_type_t enum: { #H5L_TYPE_ERROR -> -1. #H5L_TYPE_HARD -> 0. #H5L_TYPE_SOFT -> 1. #H5L_TYPE_UD -> 2. #H5L_TYPE_EXTERNAL -> 3. } H5T_cset_t enum: { #H5T_CSET_ERROR -> -1. #H5T_CSET_ASCII -> 0. #H5T_CSET_UTF8 -> 1. }
另外,hbool_t在HDF5中通常是unsigned char,可以映射为#bool或者#uchar,根据你的Squeak FFI配置调整即可。
结构体基础定义
接下来定义H5L_info_t的结构体,用联合中最大成员的类型替换联合部分。观察你的结构体:
haddr_t是HDF5的地址类型,64位系统下是8字节无符号整数;size_t也是8字节无符号整数(64位系统);
两者尺寸相同,直接用#longLong(对应64位整数)来覆盖联合的内存:
H5LInfoT struct: { #type -> #H5L_type_t. #corder_valid -> #hbool_t. #corder -> #int64. #cset -> #H5T_cset_t. #u_storage -> #longLong. "这个字段对应C中的union u,共享内存区域" }
为了方便使用,我们给H5LInfoT类添加两个访问方法,根据type字段来正确解析u_storage的值:
H5LInfoT>>address "获取硬链接对应的address,仅当type为H5L_TYPE_HARD时有效" self assert: self type equals: H5L_type_t H5L_TYPE_HARD. ^ self u_storage asUnsignedInteger "haddr_t是无符号类型" H5LInfoT>>valSize "获取软链接/UD链接对应的val_size,仅当type为H5L_TYPE_SOFT或H5L_TYPE_UD时有效" self assert: (self type = H5L_type_t H5L_TYPE_SOFT or: [self type = H5L_type_t H5L_TYPE_UD]). ^ self u_storage asUnsignedInteger "size_t是无符号类型"
这里加了断言来避免错误访问,你也可以根据需求去掉断言,改成返回nil或者其他默认值。
为了确保Squeak中定义的结构体和C中的H5L_info_t内存布局一致,你可以打印结构体的字节大小,和C中sizeof(H5L_info_t)的结果对比:
Transcript show: 'Squeak struct size: ', H5LInfoT byteSize printString; cr.
如果大小不一致,通常是内存对齐的问题,可以检查每个字段的类型是否正确,或者在结构体定义中手动添加对齐字段(比如#pad -> #uchar之类的,根据实际对齐需求调整)。
当你调用HDF5的H5Lget_info函数获取到H5LInfoT实例后,就可以这样使用:
| info | info := H5Lget_info: locationId name: linkName info: H5LInfoT new. info type == H5L_type_t H5L_TYPE_HARD ifTrue: [ Transcript show: '硬链接地址: ', info address printString; cr. ]. (info type == H5L_type_t H5L_TYPE_SOFT or: [info type == H5L_type_t H5L_TYPE_UD]) ifTrue: [ Transcript show: '链接值大小: ', info valSize printString; cr. ].
如果你的平台上haddr_t和size_t尺寸不同(比如32位系统下是4字节),或者联合中有更大的成员,只需要把u_storage换成最大成员对应的类型即可。比如如果有一个成员是16字节的数组,就用#longLong数组或者#byte数组来映射,访问时再手动解析字节。
内容的提问来源于stack exchange,提问作者aka.nice

