将C char数组转换为String

时间:2014-12-13 05:11:33

标签: arrays swift interop tuples

我有一个与C库互操作的Swift程序。这个C库返回一个内部带有char[]数组的结构,如下所示:

struct record
{
    char name[8];
};

将定义正确导入Swift。但是,该字段被解释为8个Int8元素的元组(类型为(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)),我不知道如何使用Swift转换为String

没有String初始值设定项接受Int8元组,并且似乎无法获得指向元组的第一个元素的指针(因为类型可能是异构的,这不是真的令人惊讶)。

现在,我最好的想法是创建一个微小的C函数,它接受指向结构本身的指针并返回name作为char*指针而不是数组,然后继续使用它。 / p>

然而,是否有纯粹的Swift方式呢?

9 个答案:

答案 0 :(得分:26)

C数组char name[8]作为元组导入Swift:

(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)

name的地址与name[0]的地址相同,并且 Swift 保留从C导入的结构的内存布局,如 confirmed by Apple engineer Joe Groff:

  

...您可以保留在C中定义的结构并将其导入Swift。斯威夫特将尊重C的布局。

因此,我们可以传递record.name的地址, 转换为UInt8指针,转换为 String初始化器:

var record = someFunctionReturningAStructRecord()

// Swift 2:
let name = withUnsafePointer(&record.name) {
    String.fromCString(UnsafePointer($0))!
}

// Swift 3:
let name = withUnsafePointer(to: &record.name) {
    $0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: record.name)) {
        String(cString: $0)
    }
}

注意:假设name[]中的字节是有效的NUL终止的UTF-8序列。

答案 1 :(得分:3)

您可以使用Swift的可变参数语法将元组实际收集到数组中:

let record = getRecord()
let (int8s: Int8...) = myRecord          // int8s is an [Int8]
let uint8s = int8s.map { UInt8($0) }
let string = String(bytes: uint8s, encoding: NSASCIIStringEncoding)
// myString == Optional("12345678")

答案 2 :(得分:2)

我也有兴趣为自己的目的而努力,所以我添加了一个新功能:

func asciiCArrayToSwiftString(cString:Int8...) -> String
{
    var swiftString = String()            // The Swift String to be Returned is Intialized to an Empty String
    var workingCharacter:UnicodeScalar = UnicodeScalar(UInt8(cString[0]))
    var count:Int = cString.count

    for var i:Int = 0; i < count; i++
    {
        workingCharacter = UnicodeScalar(UInt8(cString[i])) // Convert the Int8 Character to a Unicode Scalar
        swiftString.append(workingCharacter)             // Append the Unicode Scalar

    }

    return swiftString                     // Return the Swift String
}

我用以下方法调用此函数:

    let t:Int8 = Int8(116)
    let e:Int8 = Int8(101)
    let s:Int8 = Int8(115)
    let testCString = (t, e, s, t)
    let testSwiftString = wispStringConverter.asciiCArrayToSwiftString(testCString.0, testCString.1, testCString.2, testCString.3)
    println("testSwiftString = \(testSwiftString)")

结果输出为:

testSwiftString = test

答案 3 :(得分:2)

Swift 3.仅使用反射。此版本在遇到空字节时停止构建字符串。测试

func TupleOfInt8sToString( _ tupleOfInt8s:Any ) -> String? {
    var result:String? = nil
    let mirror = Mirror(reflecting: tupleOfInt8s)

    for child in mirror.children {
        guard let characterValue = child.value as? Int8, characterValue != 0 else {
            break
        }

        if result == nil {
            result = String()
        }
        result?.append(Character(UnicodeScalar(UInt8(characterValue))))
    }

    return result
}

答案 4 :(得分:2)

我刚刚使用Swift 3遇到过类似的问题。(3.0.2)。我试图将一个CChar数组,[CChar]转换为Swift中的String。事实证明,Swift 3有一个String初始化器,它将采用cString。

示例:

let a = "abc".cString(using: .utf8) // type of a is [CChar]
let b = String(cString: a!, encoding: .utf8) // type of b is String
print("a = \(a)")
print("b = \(b)")

结果

a =可选([97,98,99,0])

b =可选(&#34; abc&#34;)

请注意,String上的cString函数会产生一个Optional。在String.init函数创建b时使用它必须强制解包。并且b也是可选的...意味着两者最终都可以为零,因此也应该使用错误检查。

答案 5 :(得分:1)

试试这个:

func asciiCStringToSwiftString(cString:UnsafePointer<UInt8>, maxLength:Int) -> String
{
    var swiftString = String()  // The Swift String to be Returned is Intialized to an Empty String
    var workingCharacter:UnicodeScalar = UnicodeScalar(cString[0])
    var count:Int = 0           // An Index Into the C String Array Starting With the First Character

    while cString[count] != 0             // While We Haven't reached the End of the String
    {
        workingCharacter = UnicodeScalar(cString[count]) // Convert the ASCII Character to a Unicode Scalar
        swiftString.append(workingCharacter)             // Append the Unicode Scalar Version of the ASCII Character
        count++                                          // Increment the Index to Look at the Next ASCII Character

        if count > maxLength                            // Set a Limit In Case the C string was Not NULL Terminated
        {
            if printDebugLogs == true
            {
                swiftString="Reached String Length Limit in Converting ASCII C String To Swift String"
            }
            return swiftString
        }
    }

    return swiftString                     // Return the Swift String
}

答案 6 :(得分:1)

这是我提出的一个解决方案,它使用反射实际将元组转换为[Int8](请参阅Any way to iterate a tuple in swift?),然后使用fromCString ...()方法将其转换为字符串。

func arrayForTuple<T,E>(tuple:T) -> [E] {
    let reflection = reflect(tuple)
    var arr : [E] = []
    for i in 0..<reflection.count {
        if let value = reflection[i].1.value as? E {
            arr.append(value)
        }
    }
    return arr
}

public extension String {
    public static func fromTuple<T>(tuple:T) -> String? {
        var charArray = arrayForTuple(tuple) as [Int8]
        var nameString = String.fromCString(UnsafePointer<CChar>(charArray))
        if nameString == nil {
            nameString = String.fromCStringRepairingIllFormedUTF8(UnsafePointer<CChar>(charArray)).0
        }
        return nameString
    }
}

答案 7 :(得分:0)

详细信息

  • Xcode 11.2.1(11B500),Swift 5.1

解决方案

...

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'token',
        'provider' => 'users',
        'hash' => false,
    ],

    'foo' => [
        'driver' => 'session',
        'provider' => 'foo',
    ],
],


...

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\User::class,
    ],

    'foo' => [
        'driver' => 'foo',             // Using a 3rd party for auth.
        'model' => App\MyUser::class,  // User model for auth.
    ],

    // 'users' => [
    //     'driver' => 'database',
    //     'table' => 'users',
    // ],
],

用法(完整样本)

  

使用 C语言(Header.h)的代码

extension String {
    init?(fromTuple value: Any) {
        guard let string = Tuple(value).toString() else { return nil }
        self = string
    }

    init?(cString: UnsafeMutablePointer<Int8>?) {
        guard let cString = cString else { return nil }
        self = String(cString: cString)
    }

    init?(cString: UnsafeMutablePointer<CUnsignedChar>?) {
        guard let cString = cString else { return nil }
        self = String(cString: cString)
    }

    init? (cString: Any) {

        if let pointer = cString as? UnsafeMutablePointer<CChar> {
            self = String(cString: pointer)
            return
        }

        if let pointer = cString as? UnsafeMutablePointer<CUnsignedChar> {
            self = String(cString: pointer)
            return
        }

        if let string = String(fromTuple: cString) {
            self = string
            return
        }

        return nil
    }
}

// https://stackoverflow.com/a/58869882/4488252

struct Tuple<T> {
    let original: T
    private let array: [Mirror.Child]
    init(_ value: T) {
        self.original = value
        array = Array(Mirror(reflecting: original).children)
    }
    func compactMap<V>(_ transform: (Mirror.Child) -> V?) -> [V] { array.compactMap(transform) }

    func toString() -> String? {

        let chars = compactMap { (_, value) -> String? in
            var scalar: Unicode.Scalar!
            switch value {
            case is CUnsignedChar: scalar = .init(value as! CUnsignedChar)
            case is CChar: scalar = .init(UInt8(value as! CChar))
            default: break
            }
            guard let _scalar = scalar else { return nil }
            return String(_scalar)
        }
        if chars.isEmpty && !array.isEmpty { return nil }
        return chars.joined()
    }
}
  

...- Bridging-Header.h

#ifndef Header_h
#define Header_h

#ifdef __cplusplus
extern "C" {
#endif

char c_str1[] = "Hello world!";
char c_str2[50] = "Hello world!";
char *c_str3 = c_str2;

typedef unsigned char UTF8CHAR;
UTF8CHAR c_str4[] = {72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 32, 0};
UTF8CHAR *c_str5 = c_str4;
UTF8CHAR c_str6[] = {'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\0'};
UTF8CHAR *c_str7 = 0;
UTF8CHAR *c_str8 = "";

#define UI BYTE

#ifdef __cplusplus
}
#endif

#endif /* Header_h */
  

快捷代码

#include "Header.h"

输出

func test() {
    printInfo(c_str1)
    printInfo(c_str2)
    printInfo(c_str3)
    printInfo(c_str4)
    printInfo(c_str5)
    printInfo(c_str6)
    printInfo(c_str7)
    printInfo(c_str8)

    print(String(fromTuple: c_str1) as Any)
    print(String(fromTuple: c_str2) as Any)
    print(String(cString: c_str3) as Any)
    print(String(fromTuple: c_str4) as Any)
    print(String(cString: c_str5) as Any)
    print(String(fromTuple: c_str6) as Any)
    print(String(fromTuple: c_str7) as Any)
    print(String(cString: c_str8) as Any)
}

var counter = 1;

func printInfo(_ value: Any?) {
    print("name: str_\(counter)")
    counter += 1
    guard let value = value else { return }
    print("type: \(type(of: value))")
    print("value: \(value)")
    print("swift string: \(String(cString: value))")
    print("\n-----------------")
}

答案 8 :(得分:0)

关于此主题已经有多个答案,但是没有一个答案是简单的一行,也不能解决非null终止的答案。

假设字符串NULL被终止:

struct record {
    char name[8];
};

//Might by unsafe, depends
String(cString: &record.name.0)

//Safe
String(cString: unsafeBitCast(UnsafePointer(&record.name), to: UnsafePointer<Int8>.self))

对于未NULL终止的字符串:

//Might by unsafe, depends
String(cString: &record.name.0).prefix(MemoryLayout.size(ofValue: record.name))

//Safe
String(bytesNoCopy: UnsafeMutableRawPointer(mutating: &record.name), length: MemoryLayout.size(ofValue: record.name), encoding: .utf8, freeWhenDone: false)

––––

关于@MartinR仅传递一个字节的问题,您也可以传递指向整个变量的指针,但是就我个人而言,我从未经历过仅传递一个字节的快速操作,因此应该是安全的。