Swift转换C的uint64_t与使用自己的UInt64类型不同

我正在将应用程序从(Objective-)C移植到Swift,但必须使用用C编写的第三方框架。有一些不兼容的东西,比如typedef,它们被解释为Int,但必须传递给框架的function如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回调函数bitcast到这种类型。 所以我使用闭包语法来定义回调函数:

 let myMessageHandler : MessageHandlerProc = { (deviceID : UInt32, msgType : UInt32, var msgArgPtr : UnsafeMutablePointer) -> Void in ... } 

我将上述结构转换为原始post的不同结构。

和不!统计信息定义为Swift Array不起作用。 Swift中的Array不等同于C中的Array,因为Swift的Array是扩展类型。 使用指针写入和读取它会导致exception

在此处输入图像描述

只有Tuples在Swift中本机实现,你可以在它上面指针来回运行。

好的…这样可以正常工作,只要有数据可以调用我的回调函数。

因此,在myMessageHandler中,我想在msgArgPtr中使用存储的数据,这是一个void指针,因此必须转换为DeviceState

 let state = (UnsafeMutablePointer(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 ) { ... let _locMS:MyStruct = (UnsafeMutablePointer(p)).memory ... } 

_locMS中的数据与C代码中的数据不同。 只有在我的C代码中使用pragma更改struct packing时才会出现此问题; 如果使用默认对齐方式,则上述不安全转换正常工作。 可以解决这个问题如下:

 let _locMS:MyStruct = MyStruct(_apis: (UnsafeMutablePointer(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 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 ) { ... 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