Swift converts C uint64_t to another than using native UInt64 type

I am porting an application from (Objective-) C to Swift, but I need to use a third-party structure written in C. There are several incompatibilities, such as typedefs, which are interpreted as Int, but must be passed to structure functions like UInts or the like. Therefore, in order to avoid constant casting operations throughout the entire Swift application, I decided to transfer the C header files to Swift, having all types, since they should be in one place for me.

I managed to transfer almost everything and overcome many obstacles, but this one:

The C header defines a structure containing the uint64_t variable among others. This structure is used to pass data to the callback function as a pointer. The callback function takes a void pointer as an argument, and I have to use it in the UnsafeMutablePointer operation for a structure type (or another header structure, if necessary). All casting and memory access works fine as long as I use the original structure from the C header, which was automatically converted by Swift upon import.

Manual structure replication in Swift does not perform byte-fitting.

Let me show you an example of this situation:

Inside the CApiHeader.h file there is something like

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

In my opinion, there should be an Swift equivalent here

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

Or what should also work is a tuple designation

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

This works fine, but not immediately, as soon as the UInt64 type exists.

Ok, so what's going on?

By pointing to one of my own Swift MyStruct implementations, the hole data is shifted by 2 bytes, starting with the UInt64 field. Therefore, in this example, both arr fields are not in the correct position, but inside the UInt64 bit, which should be 64 numbers. Thus, it seams that the UInt64 field has only 48 bits.

This confirms my observation that if I replaced the UIn64 variable with this alternative

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

or this

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

(or equivalent tuple notation), it correctly aligns arr fields. But, as you can easily guess, var02 does not contain directly used data, because it is divided into several address ranges. This is even worse with the first alternative, because it is the seams that Swift fills the gap between the field is reserved and the var02 field with 16 bits is missing / shifted 2 bytes I mentioned above, but they are not available.

So, I did not understand any equivalent conversion of the C structure to Swift.

What exactly happens here and how does Swift actually transform the structure from the C header?

Do you guys have a clue or explanation or even a solution for me?

Update

The C framework has an API function with this signature:

 int16_t setHandlers(MessageHandlerProc messageHandler); 

MessageHandlerProc is a type of procedure:

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

So setHandlers is a C routine inside a framework that gets a pointer to a callback function. This callback function should provide a void pointer argument that gets cast, for example,

 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 messageHandlerProc with agreement syntax (c), so the type of procedure is available directly. On the other hand, it is not possible to use the standard func syntax and the bit function of my callHandler callback for this type. 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 structure to different structures of my original post.

And no! Definition of statistics , since Swift Array does not work. An array in Swift, not equivalent to an array in C, because Swift Array is an extended type. Writing and reading from it with a pointer throws an exception

enter image description here

Only tuples are built into Swift, and you can run back and forth using pointers.

Ok ... this works fine, and my callback function is called whenever data is available.

So, inside myMessageHandler I want to use the saved data inside msgArgPtr , which is a pointer to void and therefore needs to be translated into DeviceState .

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

Status Access:

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

Whenever I use the automatically created DeviceState Swift Pendant, everything works fine. The temporary variable has a Unix timestamp and the following statistics (available with the tuple syntax !!!) is all where they belong.

Using my manually implemented structure leads to a completely meaningless time stamp value, and the statistics fields move to the left (in the time field) - this is probably why the timestamp value is useless, because it contains bits from the array "array"). Thus, in the last two fields of statistics, I get values ​​from complexValueOld and the first axis field - with all overflows, of course.

While I'm ready to sacrifice the time value and change the UInt64 variable either as a tuple of two UInt32 types, or by changing it to UInt32 type and adding an auxiliary variable of type UInt16 to time , I get an array of stats with the correct alignment.

Have a nice day!: -)

Martin

+3
source share
2 answers

This is an update of my previous answer after reading your updated question and experimenting. I believe the problem is the mismatch between the imported C structure and the one you manually implemented in Swift. The problem can be solved using the helper function C to get an instance of C struct from the void pointer, as suggested yesterday, which can then be converted to a Swift structure implemented manually.

I was able to reproduce the problem after creating an abbreviated layout of your DeviceState structure, which 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 custom Swift native structure:

 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 environment you are working with could be compiled using a different structured package, resulting in inconsistent alignment. I used

 #pragma pack(2) 

in my C code to break the bit match between the native and imported Swift construct.

If I'm something like

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

the data in _locMS is different from the data placed there by code C. This problem only occurs if I change the structure of the structure using the pragma in my C code; the above unsafe conversion works fine if default alignment is used. You can solve this problem as follows:

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

By the way, the Swift method imports the C structure, the members of the array become tuples; this is evident from the fact that you need to use tuple notation to access them in Swift.

I have an example Xcode project illustrating all this that I posted on github:

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

Obviously, the approach of using a helper function C to get an APIStruct from a void pointer, and then convert APIStruct to MyStruct, may or may not be an option, depending on how the structures are used, how large they are, and application performance requirements. As you can say, this approach involves some copying of the structure. Other approaches, I think, include writing a C layer between Swift code and a third-party C structure, examining the memory layout of the C structure and accessing it creatively (can easily break) using the imported C structure more widely in your Swift code and etc.

Here is a way to exchange data between C and Swift code without unnecessary copying and with changes made to Swift that are visible to C code. However, given the following approach, you need to remember about the lifetime of the object and other memory management issues. You 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! ... } 
+2
source

Your two definitions are not equivalent. The array does not match the tuple. Your C struct gives 24 bytes (see this question about why). Size in Swift depends 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 
+1
source

All Articles