我正在将应用程序从 (Objective-)C 移植到 Swift,但必须使用用 C 编写的第三方框架。有一些不兼容之处,例如 typedef,它们被解释为 Int,但必须传递给框架的功能作为 UInts 或类似的。因此,为了避免在整个 Swift 应用程序中进行持续的转换操作,我决定将 C 头文件传输到 Swift,将所有类型作为 II 需要它们放在一个位置。
我能够转移几乎所有东西并克服了很多障碍,但是这个:
C 标头定义了一个结构,其中包含一个 uint64_t 变量等。该结构用于将数据作为指针传输到回调函数。回调函数将 void 指针作为参数,我必须使用 UnsafeMutablePointer 操作将其强制转换为结构的类型(或如果合适,则为标头的另一个结构)。只要我使用由 Swift 在导入时自动转换的 C 标头中的原始结构,所有转换和内存访问都可以正常工作。
然而,在 Swift 中手动复制结构并不“适合字节”。
让我向您展示这种情况的简化示例:
在 CApiHeader.h 文件里面有类似
typedef struct{
uint32_t var01;
uint64_t var02;
uint8_t arr[2];
}MyStruct, *MyStructPtr;
据我了解,这里应该是 Swift 等价物
struct MyStruct{
var01: UInt32
var02: UInt64
arr: (UInt8, UInt8)
}
或者这个元组符号也应该起作用
typealias MyStruct = (
var01: UInt32,
var02: UInt64,
arr: (UInt8, UInt8)
)
这可以正常工作,但只要有 UInt64 类型就不会。
好的,那会发生什么?
将指针转换为我自己的 Swift MyStruct 实现之一,孔数据从 UInt64 字段开始移动了 2 个字节。所以在这个例子中,两个arr字段都不在正确的位置,而是在 UInt64 位内,应该是 64 位。因此,UInt64 字段似乎只有 48 位。
这符合我的观察,如果我用这个替代替换 UIn64 变量
struct MyStruct{
var01: UInt32
reserved: UInt16
var02: UInt32
arr: (UInt8, UInt8)
}
或者这个
struct MyStruct{
var01: UInt32
var02: (UInt32, UInt32)
arr: (UInt8, UInt8)
}
(或等效的元组表示法)它正确对齐arr字段。但是你可以很容易地猜到var02包含不直接可用的数据,因为它被分割成多个地址范围。第一种选择更糟糕,因为它缝合了 Swift 用 16 位填充了保留字段和var02字段之间的间隙——我上面提到的丢失/移位的 2 个字节——但这些并不容易访问。
所以我还没有想出 Swift 中 C 结构的任何等效转换。
这里到底发生了什么,Swift 如何从 C 标头转换结构?
请问你们对我有提示或解释甚至解决方案吗?
更新
C 框架有一个带有这个签名的 API 函数:
int16_t setHandlers(MessageHandlerProc messageHandler);
MessageHandlerProc 是过程类型:
typedef void (*messageHandlerProc)(unsigned int id, unsigned int messageType, void *messageArgument);
所以 setHandlers 是框架内的一个 C 过程,它获取一个指向回调函数的指针。这个回调函数必须提供一个 void 指针的参数,它被强制转换为例如
typedef struct {
uint16_t revision;
uint16_t client;
uint16_t cmd;
int16_t parameter;
int32_t value;
uint64_t time;
uint8_t stats[8];
uint16_t compoundValueOld;
int16_t axis[6];
uint16_t address;
uint32_t compoundValueNew;
} DeviceState, *DeviceStatePtr;
Swift 足够聪明,可以使用约定(c)语法导入 messageHandlerProc,因此过程类型是直接可用的。另一方面,不可能使用标准的 func 语法并将我的 messageHandler 回调函数比特转换为这种类型。所以我使用闭包语法来定义回调函数:
let myMessageHandler : MessageHandlerProc = { (deviceID : UInt32, msgType : UInt32, var msgArgPtr : UnsafeMutablePointer<Void>) -> Void in
...
}
我将上述结构转换为原始帖子的不同结构。
和不!将stats定义为 Swift Array 不起作用。Swift 中的 Array 不等同于 C 中的 Array,因为 Swift 的 Array 是扩展类型。使用指针写入和读取它会导致异常
只有元组是在 Swift 中原生实现的,你可以用指针在它上面来回运行。
好的......这一切都很好,只要数据可用,我的回调函数就会被调用。
因此,在myMessageHandler中,我想使用msgArgPtr中存储的数据,它是一个 void 指针,因此必须转换为DeviceState。
let state = (UnsafeMutablePointer<MyDeviceState>(msgArgPtr)).memory
像这样访问状态:
...
print(state.time)
print(state.stats.0)
...
每当我使用DeviceState的自动生成的 Swift 挂件时,它都能很好地工作。time 变量具有 Unix 时间戳,并且以下统计信息(可使用元组语法访问!!!)都是它们所属的位置。
然而,使用我手动实现的结构会导致完全没有意义的时间戳值,并且统计字段向左移动(朝向时间字段 - 这可能是时间戳值无用的原因,因为它包含来自统计“数组”的位) . 因此,在最后两个统计字段中,我从 CompoundValueOld 获取值和第一个轴字段中获取值 - 当然所有溢出。
只要我愿意牺牲时间值并通过两个 UInt32 类型的元组或通过将其更改为 UInt32 类型并在时间之前添加 UInt16 类型的辅助变量来更改 UInt64 变量之前添加 UInt16 类型的辅助变量来更改 UInt64 变量,我就会收到一个统计信息“阵列”正确对齐。
祝你今天过得愉快!:-)
马丁