gpt4 book ai didi

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

转载 作者:搜寻专家 更新时间:2023-10-31 22:34:22 24 4
gpt4 key购买 nike

我正在将应用程序从(目标-)C移植到Swift,但必须使用用C编写的第三方框架。有一些不兼容项,如typedef,解释为int,但必须作为uint等传递到框架的函数。因此,为了避免在整个swift应用程序中持续执行强制转换操作,我决定将C头文件传输给swift,使所有类型都在一个地方,因为我需要它们。
我几乎能转移一切,克服了很多障碍,但这一个:
C头定义了一个结构,其中包含一个uint64变量等。此结构用于将数据作为指针传输到回调函数。回调函数接受一个void指针作为参数,我必须用unsafemutablepointer操作将其强制转换为结构的类型(或者头的另一个结构,如果合适的话)。只要我使用C头中的原始结构(导入时由swift自动转换),所有的转换和内存访问就可以正常工作。
然而,在swift中手动复制结构并不是“字节匹配”。
让我向您展示一个这种情况的简化示例:
在capiheader.h文件中有

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

根据我的理解,这里应该是斯威夫特等价物
struct MyStruct{
var01: UInt32
var02: UInt64
arr: (UInt8, UInt8)
}

或者这个元组符号也应该起作用
typealias MyStruct = (
var01: UInt32,
var02: UInt64,
arr: (UInt8, UInt8)
)

这是正常工作,但不是尽快有一个uint64类型。
好吧,那会发生什么?
将指针转换到我自己的swift mystrut实现中的一个,从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不包含直接可用的数据,因为它在多个地址范围内被拆分。第一个选项的情况更糟,因为它用16位(我上面提到的丢失/移位的2个字节)填补了保留字段和var02字段之间的空白,但这些是不容易访问的。
所以我还没有在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回调函数重播到此类型。所以我使用闭包语法来定义回调函数:
let myMessageHandler : MessageHandlerProc = { (deviceID : UInt32, msgType : UInt32, var msgArgPtr : UnsafeMutablePointer<Void>) -> Void in

...

}

我把上面提到的结构转换成我原来的职位的不同结构。
不!将状态定义为swift数组不起作用。swift中的数组不等于c中的数组,因为swift的数组是扩展类型。使用指针写入并从中读取会导致异常
enter image description here
只有元组是在swift中本地实现的,您可以使用指针在它上面来回运行。
可以。。。这一切都很好,只要有数据,我的回调函数就会被调用。
所以在mymessagehandler中,我想使用msgargptr中存储的数据,它是一个空指针,因此必须强制转换到devicestate中。
let state = (UnsafeMutablePointer<MyDeviceState>(msgArgPtr)).memory

访问状态如下:
...
print(state.time)
print(state.stats.0)
...

每当我使用自动生成的快速挂件的设备,它都很好地工作。时间变量具有unix时间戳和以下状态(可通过元组语法访问!!!!)都属于他们。
然而,使用我手动实现的结构会导致一个完全没有意义的时间戳值,并且状态字段会向左移动(朝向时间字段-这可能是时间戳值无效的原因,因为它包含来自状态“数组”的位)。所以在统计的最后两个字段中,我从compoundValueOld和第一个轴字段中获取值——当然,所有的值都溢出了。
只要我愿意牺牲时间值并通过两个uint32类型的元组或将其更改为uint32类型并在时间之前添加uint16类型的辅助变量来更改uint64变量,我就会收到具有正确对齐方式的统计“数组”。
祝你今天愉快!-)
马丁

最佳答案

这是在阅读了你最新的问题并进行了更多的实验之后,我对我之前的答案的更新。我认为问题在于导入的C结构和您在swift中手动实现的结构之间的对齐不一致。该问题可以通过使用c helper函数从void指针中获取c结构的实例来解决,正如昨天建议的那样,该实例随后可以转换为手动实现的swift结构。
在创建了一个看起来像

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

lou locms中的数据与C代码中放置的数据不同。只有在C代码中使用pragma更改结构打包时才会出现此问题;如果使用默认对齐,则上述不安全的转换可以正常工作。这个问题可以解决如下:
let _locMS:MyStruct = MyStruct(_apis: (UnsafeMutablePointer<APIStruct>(p)).memory)

顺便说一句,Swift导入C结构的方式是,数组成员成为元组;这可以从必须使用元组符号才能在Swift中访问它们的事实中看出。
我有一个Xcode项目示例,演示了我在Github上放置的所有这些内容:
https://github.com/omniprog/xcode-samples

显然,使用helper c函数从一个空指针中获取apinstruct,然后将apinstruct转换为mystruct的方法可能是或不是一个选项,这取决于结构的使用方式、大小以及应用程序的性能要求。如您所知,这种方法涉及到结构的一些复制。我认为,其他方法包括在Swift代码和第三方C框架之间编写一个C层,研究C结构的内存布局并以创造性的方式访问它(可能很容易中断),在Swift代码中更广泛地使用导入的C结构等。
这里有一种方法可以在C和Swift代码之间共享数据,而不必进行不必要的复制,并且在C代码可见的Swift中进行更改。但是,使用以下方法,必须了解对象生存期和其他内存管理问题。可以创建一个类,如下所示:
// 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!
...
}

关于objective-c - Swift转换C的uint64_t与使用自己的UInt64类型不同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33424316/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com