[英]Swift converts C's uint64_t different than it uses its own UInt64 type
我正在將應用程序從(Objective-)C移植到Swift,但必須使用用C編寫的第三方框架。有一些不兼容的東西,比如typedef,它們被解釋為Int,但必須傳遞給框架的功能如UInts等。 因此,為了避免整個Swift應用程序中的常量轉換操作,我決定將C頭文件傳輸到Swift,所有類型的II都需要它們在一個地方。
我能夠轉移幾乎所有的東西,並克服了很多障礙,但這一個:
C頭定義了一個包含uint64_t變量的結構。 此結構用於將數據作為指針傳輸到回調函數。 回調函數將void指針作為參數使用,我必須使用UnsafeMutablePointer操作將其轉換為struct的類型(或者如果合適的話,還是頭的另一個結構)。 只要我使用導入時由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>) -> Void in
...
}
我將上述結構轉換為原始帖子的不同結構。
和不! 將統計信息定義為Swift Array不起作用。 Swift中的數組不等同於C中的數組,因為Swift的數組是擴展類型。 使用指針寫入和讀取它會導致異常
只有Tuples在Swift中本機實現,你可以在它上面指針來回運行。
好的...這樣可以正常工作,只要數據可用,我的回調函數就會被調用。
因此,在myMessageHandler中,我想在msgArgPtr中使用存儲的數據,這是一個void指針,因此必須轉換為DeviceState 。
let state = (UnsafeMutablePointer<MyDeviceState>(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<Void> )
{
...
let _locMS:MyStruct = (UnsafeMutablePointer<MyStruct>(p)).memory
...
}
_locMS中的數據與C代碼中的數據不同。 如果我在我的C代碼中使用pragma更改struct packing,則只會出現此問題; 如果使用默認對齊方式,則上述不安全轉換可以正常工作。 可以解決這個問題如下:
let _locMS:MyStruct = MyStruct(_apis: (UnsafeMutablePointer<APIStruct>(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<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
}
然后我們可以使用這個結構如下:
func swiftCallBackVoid( p: UnsafeMutablePointer<Void> )
{
...
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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.