【问题标题】:Converting a C string inside a struct to a Swift String将结构内的 C 字符串转换为 Swift 字符串
【发布时间】:2014-06-24 01:01:58
【问题描述】:

我在 Swift 应用程序中使用现有的 C 库并尝试将 C 字符缓冲区转换为 Swift 字符串。

桥接.h

typedef struct { char mfg[8]; char model[8]; } motorcycle;

void GetMotorcycle(motorcycle *m);

Example.swift

var cycle = motorcycle(mfg: (0,0,0,0,0,0,0,0), model: (0,0,0,0,0,0,0,0));
GetMotorcycle(&cycle)
var manufacturer : String = String.fromCString(cycle.mfg)     // Error

这会产生“找不到接受提供的参数的 'fromCString' 的重载”

由于 Swift 将 C 字符数组视为元组,我找不到将其转换为 Swift 字符串的方法。

【问题讨论】:

    标签: struct tuples swift


    【解决方案1】:

    好吧,至少那个 C 字符数组只有八个字符长,因为目前没有办法遍历一个元组。以下是转换它的方法:

    func char8ToString(tuple: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar)) -> String {
        let arr: unichar[] = [unichar(tuple.0), unichar(tuple.1),
                            unichar(tuple.2), unichar(tuple.3),
                            unichar(tuple.4), unichar(tuple.5),
                            unichar(tuple.6), unichar(tuple.7)]
        let len = arr.reduce(0) { $1 != 0 ? $0 + 1 : $0 }
        return NSString(characters: arr, length: len)
    }
    
    var manufacturer: String = char8ToString(cycle.mfg)
    

    希望我们能尽快获得语言支持,以便更好地处理此案例!

    【讨论】:

    • 使用 char8ToString() 生成的字符串尾随 0 个字符。假设 mfg=BMW 和 model=K1200S,这个 NSLog 语句:NSLog("mfg = (char8ToString(cycle.mfg)), model = (char8ToString(cycle.model))") 产生:"mfg = BMW"
    • 谢谢 - 更新了我的答案以删除尾随的空字符。
    • 显然,您可以按此处所述迭代元组:stackoverflow.com/questions/24299045/…
    【解决方案2】:

    过去几天我一直在同一条船上。我最终提出了一个通用方案来处理来自 C API 的 C 结构中的 C 数组,如果必须处理大型数组,那很好,但它必须使用“unsafeBitCast()”。它还必须适用于每个固定的 C 数组。这是回答当前问题的改编(代码已经过测试并且正在运行):

    // This is our C array in this case char[8]
    typealias CArray8Chars = (
        CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar
    )
    let zero8Chars: CArray8Chars = (
        0, 0, 0, 0, 0, 0, 0, 0
    )
    struct CString8  {
        private var value: CArray8Chars = zero8Chars
        var asString: String {
            mutating get {
                var cString = [CChar]()
                for i in 0..<sizeofValue(value) {
                    cString += [self[i]]
                    if self[i] == 0 {
                        break
                    }
                }
                return String.fromCStringRepairingIllFormedUTF8(&cString).0!
            }
            set {
                let cchars = Array(newValue.utf8).map { CChar(Int8(bitPattern: UInt8($0))) }
                for i in 0..<min(cchars.count,sizeof(CArray8Chars))-1 { self[i] = cchars[i] }
                self[min(cchars.count,sizeof(CArray8Chars))-1] = 0
            }
        }
        subscript(i: Int) -> CChar {
            mutating get {
                let bytePtr = withUnsafePointer(&value, { (ptr) -> UnsafePointer<CChar> in
                    return unsafeBitCast(ptr, UnsafePointer<CChar>.self)
                })
                return bytePtr[i]
            }
            set {
                let bytePtr = withUnsafeMutablePointer(&value, { (ptr) -> UnsafeMutablePointer<CChar> in
                    return unsafeBitCast(ptr, UnsafeMutablePointer<CChar>.self)
                })
                bytePtr[i] = newValue
            }
        }
    }
    

    现在,对于摩托车 C 结构,它将如下所示:

    struct MotorCycle {
        var mfg     = CString8()
        var model   = CString8()
    }
    var cycle = MotorCycle()
    GetMotorcycle(&cycle)
    var manufacturer = cycle.mfg.asString // Since the string comes from C it works fine 
    

    这也允许使用 Unicode 字符串设置 mfg/model,但需要注意:

    // "?" utf8 encoding makes the C string too big, so it gets cut. What's left represents an unknown char
    cycle.model.asString = "I do ? Motor Bikes"
    println(cycle.model.asString)
    

    并使元组可索引:

    println(cycle.mfg[2])
    cycle.model[2] = 32 // space. They are CChars, thus Int8
    

    如果喜欢 CString8 可以进一步扩展,允许获取/设置子字符串等。但这超出了这个问题的范围。

    【讨论】:

      【解决方案3】:

      Swift 的内置 reflect()MirrorType 非常简洁,允许我们检查元组、确定其长度并迭代其内容。

      let tuple = (100, 120, 49, 100)
      let mirror = reflect(tuple)
      
      var string = String()
      for index in 0..<mirror.count {
          let value = mirror[index].1.value as! Int
          let character = UnicodeScalar(value)
          string.append(character)
      }
      
      println(string) // prints "dx1d"
      

      【讨论】:

        【解决方案4】:

        这是一个适用于 swift 2.1 的类型安全解决方案。

        func tuple2string<T>(tuple: T) -> String {
            var result = Array<CChar>()
            let mirror = Mirror(reflecting: tuple)
            for child in mirror.children {
                if let value = child.value as? CChar {
                    result.append(value)
                }
            }
            result.append(CChar(0))  // Null terminate
        
            return String.fromCString(result) ?? ""
        }
        

        但是,对于较大的数组,这非常慢,因为它需要检查每个字符的类型,因为编译器无法知道传入的元组是否都是相同的类型。

        【讨论】:

          【解决方案5】:

          迅速 3:

          String(cString: UnsafeRawPointer([myTuple]).assumingMemoryBound(to: CChar.self))
          

          【讨论】:

            【解决方案6】:

            这是 Swift 3Swift 4 支持的解决方案。

            func stringFromCString(tupleOfBytes: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar)) -> String {
                var bytes = tupleOfBytes
                let string = withUnsafePointer(to: &bytes) { ptr -> String in
                    return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self))
                }
                return string
            }
            

            或者,如果您想将行内用作具有字节元组的函数,请尝试此操作。

            let string = withUnsafePointer(to: &tupleOfBytes) { ptr -> String in
                return String(cString: UnsafeRawPointer(ptr).assumingMemoryBound(to: CChar.self))
            }
            return string
            

            【讨论】:

              【解决方案7】:

              Swift 5.2——这个解决方案可以接受任意长度的元组,不做任何额外的复制,并使用最新的安全内存绑定约定。

              extension String {
                  init<T>(tupleOfCChars: T, length: Int = Int.max) {
                      self = withUnsafePointer(to: tupleOfCChars) {
                          let lengthOfTuple = MemoryLayout<T>.size / MemoryLayout<CChar>.size
                          return $0.withMemoryRebound(to: UInt8.self, capacity: lengthOfTuple) {
                              String(bytes: UnsafeBufferPointer(start: $0, count: Swift.min(length, lengthOfTuple)), encoding: .utf8)!
                          }
                      }
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-06-20
                • 2022-01-11
                • 2021-06-28
                • 1970-01-01
                • 1970-01-01
                • 2016-11-20
                相关资源
                最近更新 更多