4

我正在将应用程序从 (Objective-)C 移植到 Swift,但必须使用用 C 编写的第三方框架。有一些不兼容之处,例如 typedef,它们被解释为 Int,但必须传递给框架的功能作为 UInts 或类似的。因此,为了避免在整个 Swift 应用程序中进行持续的转换操作,我决定将 C 头文件传输到 Swift,将所有类型作为 II 需要它们放在一个位置。

我能够转移几乎所有东西并克服了很多障碍,但是这个:

C 标头定义了一个结构,其中包含一个 uint64_t 变量等。该结构用于将数据作为指针传输到回调函数。回调函数将 void 指针作为参数,我必须使用 UnsafeMutablePointer 操作将其强制转换为结构的类型(或如果合适,则为标头的另一个结构)。只要我使用由 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 回调函数比特转换为这种类型。所以我使用闭包语法来定义回调函数:

let myMessageHandler : MessageHandlerProc = { (deviceID : UInt32, msgType : UInt32, var msgArgPtr : UnsafeMutablePointer<Void>) -> Void in

...

}

我将上述结构转换为原始帖子的不同结构。

和不!将stats定义为 Swift Array 不起作用。Swift 中的 Array 不等同于 C 中的 Array,因为 Swift 的 Array 是扩展类型。使用指针写入和读取它会导致异常

在此处输入图像描述

只有元组是在 Swift 中原生实现的,你可以用指针在它上面来回运行。

好的......这一切都很好,只要数据可用,我的回调函数就会被调用。

因此,在myMessageHandler中,我想使用msgArgPtr中存储的数据,它是一个 void 指针,因此必须转换为DeviceState

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

像这样访问状态

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

每当我使用DeviceState的自动生成的 Swift 挂件时,它都能很好地工作。time 变量具有 Unix 时间戳,并且以下统计信息(可使用元组语法访问!!!)都是它们所属的位置。

然而,使用我手动实现的结构会导致完全没有意义的时间戳值,并且统计字段向左移动(朝向时间字段 - 这可能是时间戳值无用的原因,因为它包含来自统计“数组”的位) . 因此,在最后两个统计字段中,我从 CompoundValueOld 获取和第一个字段中获取值 - 当然所有溢出。

只要我愿意牺牲时间值并通过两个 UInt32 类型的元组或通过将其更改为 UInt32 类型并在时间之前添加 UInt16 类型的辅助变量来更改 UInt64 变量之前添加 UInt16 类型的辅助变量来更改 UInt64 变量,我就会收到一个统计信息“阵列”正确对齐。

祝你今天过得愉快!:-)

马丁

4

2 回答 2

3

在阅读您更新的问题并进行更多试验后,这是对我之前答案的更新。我认为问题在于导入的 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 代码中使用编译指示更改结构打包时才会出现此问题;如果使用默认对齐方式,上述不安全的转换工作正常。可以通过以下方式解决此问题:

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

顺便说一句,Swift 导入 C 结构的方式,数组成员变成了元组;这可以从在 Swift 中必须使用元组表示法来访问它们的事实中看出。

我有一个示例 Xcode 项目,说明了我放在 github 上的所有内容:

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

显然,使用辅助 C 函数从 void 指针获取 APIStruct 然后将 APIStruct 转换为 MyStruct 的方法可能是一种选择,也可能不是一种选择,具体取决于结构的使用方式、它们的大小以及性能要求的应用程序。如您所知,这种方法涉及对结构的一些复制。我认为其他方法包括在 Swift 代码和第 3 方 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!
   ...
}
于 2015-10-30T04:00:28.290 回答
1

你的两个定义是不等价的。数组与元组不同。您的 Cstruct提供 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
于 2015-10-29T23:22:49.527 回答