简体   繁体   English

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

[英]Swift converts C's uint64_t different than it uses its own UInt64 type

I am in the process of porting an application from (Objective-)C to Swift but have to use a third-party framework written in C. There are a couple of incompatibilities like typedefs that are interpreted as Int but have to be passed to the framework's functions as UInts or the like. 我正在将应用程序从(Objective-)C移植到Swift,但必须使用用C编写的第三方框架。有一些不兼容的东西,比如typedef,它们被解释为Int,但必须传递给框架的功能如UInts等。 So to avoid constant casting operations throughout the entire Swift application I decided to transfer the C header files to Swift, having all types as II need them to be in one place. 因此,为了避免整个Swift应用程序中的常量转换操作,我决定将C头文件传输到Swift,所有类型的II都需要它们在一个地方。

I was able to transfer nearly everything and have overcome a lot of hurdles, but this one: 我能够转移几乎所有的东西,并克服了很多障碍,但这一个:

The C header defines a struct which contains a uint64_t variable among others. C头定义了一个包含uint64_t变量的结构。 This struct is used to transfer data to a callback function as a pointer. 此结构用于将数据作为指针传输到回调函数。 The callback function takes a void pointer as argument and I have to cast it with the UnsafeMutablePointer operation to the type of the struct (or another struct of the header if appropriate). 回调函数将void指针作为参数使用,我必须使用UnsafeMutablePointer操作将其转换为struct的类型(或者如果合适的话,还是头的另一个结构)。 All the casting and memory-accessing works fine as long as I use the original struct from the C header that was automatically transformed by Swift on import. 只要我使用导入时由Swift自动转换的C头中的原始结构,所有的转换和内存访问都可以正常工作。

Replicating the struct manually in Swift does not "byte-fit" however. 但是,在Swift中手动复制结构并不是“字节匹配”。

Let me show you a reduced example of this situation: 让我向您展示一个这种情况的简化示例:

Inside the CApiHeader.h file there is something like 在CApiHeader.h文件中有类似的东西

typedef struct{
  uint32_t var01;
  uint64_t var02;
  uint8_t arr[2];
}MyStruct, *MyStructPtr;

From my understanding this here should be the Swift equivalent 根据我的理解,这里应该是Swift的等价物

struct MyStruct{
  var01: UInt32
  var02: UInt64
  arr: (UInt8, UInt8)
}

Or what should also work is this tuple notation 或者也应该有效的是这种元组符号

typealias MyStruct = (
  var01: UInt32,
  var02: UInt64,
  arr: (UInt8, UInt8)
)

This works normally, but not as soon as there is an UInt64 type. 这可以正常工作,但不是只有UInt64类型。

Okay, so what happens? 好的,那会发生什么?

Casting the pointer to one of my own Swift MyStruct implementations the hole data is shifted by 2 bytes, starting at the UInt64 field. 将指针转换为我自己的Swift MyStruct实现之一,从UInt64字段开始,将孔数据移位2个字节。 So in this example the both arr fields are not at the correct position, but inside the UInt64 bits, that should be 64 in number. 因此,在此示例中,两个arr字段都不在正确的位置,但在UInt64位内,其数量应为64。 So it seams that the UInt64 field has only 48 bits. 因此,它接触到UInt64字段只有48位。

This accords to my observation that if I replace the UIn64 variable with this alternative 这符合我的观察,如果我用这个替代品替换UIn64变量

struct MyStruct{
  var01: UInt32
  reserved: UInt16
  var02: UInt32
  arr: (UInt8, UInt8)
}

or this one 或者这个

struct MyStruct{
  var01: UInt32
  var02: (UInt32, UInt32)
  arr: (UInt8, UInt8)
}

(or the equivalent tuple notation) it aligns the arr fields correctly. (或等效的元组表示法)它正确地对齐arr字段。 But as you can easily guess var02 contains not directly usable data, because it is split over multiple address ranges. 但是你可以很容易地猜到var02不包含直接可用的数据,因为它被分成多个地址范围。 It is even worse with the first alternative, because it seams that Swift fills up the gap between the reserved field and the var02 field with 16 bits - the missing / shifted 2 bytes I mentioned above - but these are not easily accessible. 第一种方案更糟糕,因为它接缝是Swift用16位填充保留字段和var02字段之间的间隙 - 我上面提到的丢失/移位的2个字节 - 但这些不容易访问。

So I haven't figured out any equivalent transformation of the C struct in Swift. 所以我还没有想到Swift中C结构的任何等价转换。

What happens here exactly and how does Swift transforms the struct from the C header actually? 这里到底发生了什么,Swift实际上如何从C头转换结构?

Do you guys have a hint or an explanation or even a solution for me, please? 你们有一个提示或解释甚至是我的解决方案吗?

Update 更新

The C framework has an API function with this signature: C框架具有带有此签名的API函数:

int16_t setHandlers(MessageHandlerProc messageHandler);

MessageHandlerProc is procedure type: MessageHandlerProc是过程类型:

typedef void (*messageHandlerProc)(unsigned int id, unsigned int messageType, void *messageArgument);

So setHandlers is a C procedure inside the framework that gets a pointer to a callback function. 所以setHandlers是框架内部的一个C程序,它获取一个指向回调函数的指针。 This callback function has to provide an argument of a void Pointer, that gets casted to eg 此回调函数必须提供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 is smart enough to import the messageHandlerProc with the convention(c) syntax, so the procedure type is directly available. Swift足够聪明,可以使用约定(c)语法导入messageHandlerProc,因此可以直接使用过程类型。 On the other hand it is not possible use the standard func syntax and bitcast my messageHandler callback function to this type. 另一方面,不可能使用标准的func语法并将我的messageHandler回调函数bitcast到这种类型。 So I used the closure syntax to define the callback function: 所以我使用闭包语法来定义回调函数:

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

...

}

I converted the above mentioned structure into the different structures of my original post. 我将上述结构转换为原始帖子的不同结构。

And No! 和不! Defining stats as Swift Array does not work. 统计信息定义为Swift Array不起作用。 An Array in Swift in not equivalent to an Array in C, because Swift's Array is a extended type. Swift中的数组不等同于C中的数组,因为Swift的数组是扩展类型。 Writing to and reading from it with a pointer causes an exception 使用指针写入和读取它会导致异常

在此输入图像描述

Only Tuples are natively implemented in Swift and you can run back and forth with pointers over it. 只有Tuples在Swift中本机实现,你可以在它上面指针来回运行。

Okay... this works all fine and my callback function gets called whenever data is available. 好的...这样可以正常工作,只要数据可用,我的回调函数就会被调用。

So inside myMessageHandler I want to use the stored Data inside msgArgPtr which is a void pointer and thus has to be cast into DeviceState . 因此,在myMessageHandler中,我想在msgArgPtr中使用存储的数据,这是一个void指针,因此必须转换为DeviceState

let state = (UnsafeMutablePointer<MyDeviceState>(msgArgPtr)).memory

Accessing state it like: 访问状态如下:

...
print(state.time)
print(state.stats.0)
...

Whenever I use the automatically generated Swift pendant of DeviceState it all works nicely. 每当我使用自动生成的DeviceState的 Swift挂件时,它都可以很好地工作。 The time variable has the Unix Time Stamp and the following stats (accessible with tuple syntax!!!) are all where they belong. 时间变量具有Unix时间戳,以下属性(可通过元组语法访问!!!)都属于它们。

Using my manually implemented struct however results in a completely senseless time stamp value and the stats fields are shifted to the left (towards the time field - that's probably why the time stamp value is useless, because it contains bits from the stats "array"). 然而,使用我手动实现的结构会产生完全无意义的时间戳值,并且统计数据字段向左移动(朝向时间字段 - 这可能是时间戳值无用的原因,因为它包含来自统计数据“数组”的位) 。 So in the last two fields of stats I get values from compoundValueOld and the first axis field - with all the overflowing of course. 因此,在统计数据的最后两个字段中,我从compoundValueOld和第一个字段获取值 - 当然所有溢出。

As long as I am willing to sacrifice the time value and change the UInt64 variable by either a tuple of two UInt32 types or by changing it to a UInt32 type and adding a auxiliary variable of the type UInt16 right before time , I receive a stats "array" with correct alignment. 只要我愿意牺牲时间值并通过两个UInt32类型的元组或通过将其更改为UInt32类型并在时间之前添加类型为UInt16的辅助变量来更改UInt64变量,我会收到一个统计信息 “数组“正确对齐。

Have a nice day! 祝你今天愉快! :-) :-)

Martin 马丁

This is an update to my earlier answer after reading your updated question and experimenting some more. 这是我在阅读您更新的问题并进行更多实验后对我之前的答案的更新。 I believe the problem is an alignment discrepancy between the imported C structure and the one you manually implemented in Swift. 我认为问题是导入的C结构与您在Swift中手动实现的结构之间的对齐差异。 The problem can be solved by using a C helper function to get an instance of the C struct from void pointer as was suggested yesterday, which can then be converted to the manually implemented Swift struct. 问题可以通过使用C辅助函数从昨天建议的void指针获取C结构的实例来解决,然后可以将其转换为手动实现的Swift结构。

I've been able to reproduce the problem after creating an abbreviated mock-up of your DeviceState structure that looks like 在创建了一个看起来像你的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;

The corresponding hand-crafted Swift native structure is: 相应的手工制作的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
}

The C framework you are working with could have been compiled using a different struct packing, resulting in a non-matching alignment. 您正在使用的C框架可能已使用不同的结构打包进行编译,从而导致不匹配的对齐。 I used 我用了

#pragma pack(2) 

in my C code to break the bit-matching between the Swift's native and imported C struct. 在我的C代码中打破Swift的本机和导入的C结构之间的位匹配。

If I do something like 如果我做的事情

func swiftCallBackVoid( p: UnsafeMutablePointer<Void> )
{
     ...
    let _locMS:MyStruct = (UnsafeMutablePointer<MyStruct>(p)).memory
     ...
}

the data in _locMS is different from what was placed there by C code. _locMS中的数据与C代码中的数据不同。 This problem only occurs if I change struct packing using a pragma in my C code; 如果我在我的C代码中使用pragma更改struct packing,则只会出现此问题; the above unsafe conversion works fine if the default alignment is used. 如果使用默认对齐方式,则上述不安全转换可以正常工作。 One can solve this problem as follows: 可以解决这个问题如下:

let _locMS:MyStruct = MyStruct(_apis: (UnsafeMutablePointer<APIStruct>(p)).memory)

BTW, the way Swift imports the C struct, the array members become tuples; 顺便说一句,Swift导入C结构的方式,数组成员成为元组; this can be seen from the fact that tuple notation has to be used to access them in Swift. 这可以从以下事实看出:必须使用元组符号在Swift中访问它们。

I have a sample Xcode project illustrating all this that I've placed on github: 我有一个示例Xcode项目,说明了我放在github上的所有这些:

https://github.com/omniprog/xcode-samples

Obviously, the approach of using a helper C function to get APIStruct from a void pointer and then converting the APIStruct to MyStruct may or may not be an option, depending on how the structures are used, how large they are, and on the performance requirements of the application. 显然,使用辅助C函数从空指针获取APIStruct然后将APIStruct转换为MyStruct的方法可能是也可能不是一种选择,具体取决于结构的使用方式,它们的大小以及性能要求申请。 As you can tell, this approach involves some copying of the structure. 如您所知,这种方法涉及对结构的一些复制。 Other approaches, I think, include writing a C-layer between Swift code and the 3rd party C framework, studying the memory layout of the C structure and accessing it in creative ways (may break easily), using the imported C struct more extensively in your Swift code, etc... 我认为其他方法包括在Swift代码和第三方C框架之间编写C层,研究C结构的内存布局并以创造性方式访问它(可能容易破解),更广泛地使用导入的C结构你的Swift代码等......

Here is a way to share data between C and Swift code without unnecessary copying and with changes made in Swift visible to C code. 这是一种在C和Swift代码之间共享数据的方法,无需进行不必要的复制,并且在Swift中对C代码进行了更改。 With the following approach, however, it's imperative to be aware of object lifetime and other memory management issues. 但是,通过以下方法,必须了解对象生存期和其他内存管理问题。 One can create a class as follows: 可以按如下方式创建一个类:

// 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
}

Then we can use this structure as follows: 然后我们可以使用这个结构如下:

func swiftCallBackVoid( p: UnsafeMutablePointer<Void> )
{
   ...
   var _myUnsafe : MyStructUnsafe = MyStructUnsafe(_p: APIStructPtr(p))
   ... 
   _myUnsafe.time = 9876543210  // this change is visible in C code!
   ...
}

Your two definitions are not equivalent. 你的两个定义不相同。 An array is not the same as a tuple. 数组与元组不同。 Your C struct gives 24 bytes (see this question as to why). 你的C struct给出了24个字节(请参阅这个问题 ,为什么)。 The size in Swift differs depend on how you implement it: 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM