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

不打扰是莪最后的温柔 提交于 2019-11-29 11:54:44

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. 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.

I've been able to reproduce the problem after creating an abbreviated mock-up of your DeviceState structure that looks like

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:

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. I used

#pragma pack(2) 

in my C code to break the bit-matching between the Swift's native and imported C struct.

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. This problem only occurs if I change struct packing using a pragma in my C code; 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; this can be seen from the fact that tuple notation has to be used to access them in Swift.

I have a sample Xcode project illustrating all this that I've placed on 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. 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...

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. 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!
   ...
}
Code Different

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). The size in Swift differs depend on how you implement it:

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!