[英]Swift converts C's uint64_t different than it uses its own UInt64 type
我正在将应用程序从(Objective-)C移植到Swift,但必须使用用C编写的第三方框架。有一些不兼容的东西,比如typedef,它们被解释为Int,但必须传递给框架的功能如UInts等。 因此,为了避免整个Swift应用程序中的常量转换操作,我决定将C头文件传输到Swift,所有类型的II都需要它们在一个地方。
我能够转移几乎所有的东西,并克服了很多障碍,但这一个:
C头定义了一个包含uint64_t变量的结构。 此结构用于将数据作为指针传输到回调函数。 回调函数将void指针作为参数使用,我必须使用UnsafeMutablePointer操作将其转换为struct的类型(或者如果合适的话,还是头的另一个结构)。 只要我使用导入时由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回调函数bitcast到这种类型。 所以我使用闭包语法来定义回调函数:
let myMessageHandler : MessageHandlerProc = { (deviceID : UInt32, msgType : UInt32, var msgArgPtr : UnsafeMutablePointer<Void>) -> Void in
...
}
我将上述结构转换为原始帖子的不同结构。
和不! 将统计信息定义为Swift Array不起作用。 Swift中的数组不等同于C中的数组,因为Swift的数组是扩展类型。 使用指针写入和读取它会导致异常
只有Tuples在Swift中本机实现,你可以在它上面指针来回运行。
好的...这样可以正常工作,只要数据可用,我的回调函数就会被调用。
因此,在myMessageHandler中,我想在msgArgPtr中使用存储的数据,这是一个void指针,因此必须转换为DeviceState 。
let state = (UnsafeMutablePointer<MyDeviceState>(msgArgPtr)).memory
访问状态如下:
...
print(state.time)
print(state.stats.0)
...
每当我使用自动生成的DeviceState的 Swift挂件时,它都可以很好地工作。 时间变量具有Unix时间戳,以下属性(可通过元组语法访问!!!)都属于它们。
然而,使用我手动实现的结构会产生完全无意义的时间戳值,并且统计数据字段向左移动(朝向时间字段 - 这可能是时间戳值无用的原因,因为它包含来自统计数据“数组”的位) 。 因此,在统计数据的最后两个字段中,我从compoundValueOld和第一个轴字段获取值 - 当然所有溢出。
只要我愿意牺牲时间值并通过两个UInt32类型的元组或通过将其更改为UInt32类型并在时间之前添加类型为UInt16的辅助变量来更改UInt64变量,我会收到一个统计信息 “数组“正确对齐。
祝你今天愉快! :-)
马丁
这是我在阅读您更新的问题并进行更多实验后对我之前的答案的更新。 我认为问题是导入的C结构与您在Swift中手动实现的结构之间的对齐差异。 问题可以通过使用C辅助函数从昨天建议的void指针获取C结构的实例来解决,然后可以将其转换为手动实现的Swift结构。
在创建了一个看起来像你的DeviceState结构的缩略模型后,我能够重现这个问题
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;
} APIStruct;
相应的手工制作的Swift原生结构是:
struct MyStruct
{
init( _apis : APIStruct)
{
revision = _apis.revision
client = _apis.client
cmd = _apis.cmd
parameter = _apis.parameter
value = _apis.value
time = _apis.time
stats = _apis.stats
compoundValueOld = _apis.compoundValueOld
}
var revision : UInt16
var client : UInt16
var cmd : UInt16
var parameter : Int16
var value : Int32
var time : UInt64
var stats : (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8);
var compoundValueOld : UInt16
}
您正在使用的C框架可能已使用不同的结构打包进行编译,从而导致不匹配的对齐。 我用了
#pragma pack(2)
在我的C代码中打破Swift的本机和导入的C结构之间的位匹配。
如果我做的事情
func swiftCallBackVoid( p: UnsafeMutablePointer<Void> )
{
...
let _locMS:MyStruct = (UnsafeMutablePointer<MyStruct>(p)).memory
...
}
_locMS中的数据与C代码中的数据不同。 如果我在我的C代码中使用pragma更改struct packing,则只会出现此问题; 如果使用默认对齐方式,则上述不安全转换可以正常工作。 可以解决这个问题如下:
let _locMS:MyStruct = MyStruct(_apis: (UnsafeMutablePointer<APIStruct>(p)).memory)
顺便说一句,Swift导入C结构的方式,数组成员成为元组; 这可以从以下事实看出:必须使用元组符号在Swift中访问它们。
我有一个示例Xcode项目,说明了我放在github上的所有这些:
https://github.com/omniprog/xcode-samples
显然,使用辅助C函数从空指针获取APIStruct然后将APIStruct转换为MyStruct的方法可能是也可能不是一种选择,具体取决于结构的使用方式,它们的大小以及性能要求申请。 如您所知,这种方法涉及对结构的一些复制。 我认为其他方法包括在Swift代码和第三方C框架之间编写C层,研究C结构的内存布局并以创造性方式访问它(可能容易破解),更广泛地使用导入的C结构你的Swift代码等......
这是一种在C和Swift代码之间共享数据的方法,无需进行不必要的复制,并且在Swift中对C代码进行了更改。 但是,通过以下方法,必须了解对象生存期和其他内存管理问题。 可以按如下方式创建一个类:
// This typealias isn't really necessary, just a convenience
typealias APIStructPtr = UnsafeMutablePointer<APIStruct>
struct MyStructUnsafe
{
init( _p : APIStructPtr )
{
pAPIStruct = _p
}
var time: UInt64 {
get {
return pAPIStruct.memory.time
}
set( newVal ) {
pAPIStruct.memory.time = newVal
}
}
var pAPIStruct: APIStructPtr
}
然后我们可以使用这个结构如下:
func swiftCallBackVoid( p: UnsafeMutablePointer<Void> )
{
...
var _myUnsafe : MyStructUnsafe = MyStructUnsafe(_p: APIStructPtr(p))
...
_myUnsafe.time = 9876543210 // this change is visible in C code!
...
}
你的两个定义不相同。 数组与元组不同。 你的C struct
给出了24个字节(请参阅这个问题 ,为什么)。 Swift的大小取决于你如何实现它:
struct MyStruct1 {
var var01: UInt32
var var02: UInt64
var arr: (UInt8, UInt8)
}
typealias MyStruct2 = (
var01: UInt32,
var02: UInt64,
arr: (UInt8, UInt8)
)
struct MyStruct3 {
var var01: UInt32
var var02: UInt64
var arr: [UInt8] = [0,0]
}
print(sizeof(MyStruct1)) // 18
print(sizeof(MyStruct2)) // 18
print(sizeof(MyStruct3)) // 24, match C's
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.