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

后端 未结 2 1876
遥遥无期
遥遥无期 2020-12-17 04:46

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

相关标签:
2条回答
  • 2020-12-17 05:06

    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
    
    0 讨论(0)
  • 2020-12-17 05:15

    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!
       ...
    }
    
    0 讨论(0)
提交回复
热议问题