将 C 字符数组转换为字符串

Posted

技术标签:

【中文标题】将 C 字符数组转换为字符串【英文标题】:Converting a C char array to a String 【发布时间】:2015-02-11 21:07:30 【问题描述】:

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

struct record

    char name[8];
;

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

没有接受Int8 元组的String 初始化程序,并且似乎不可能获得指向元组第一个元素的指针(因为类型可以是异构的,这并不奇怪)。

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

但是,有没有纯 Swift 的方式来做到这一点?

【问题讨论】:

你确定互操作使它成为一个 C 问题吗?或者你的解决方法是这样做的?特别是当你想要一个纯粹的快速解决方案时...... @Deduplicator,如果我正在寻找如何将 C char 数组转换为 Swift 字符串,我肯定会寻找标签“c”和“swift”。 该字节数组没有任何 C 语言,但您对它的描述在 C、C++、objective-C、objective-C++ 等中有效。不是 C 题。 我不知道有人将这些称为“C++ 数组”或“Objective-C 数组”或“Objective-C++ 数组”,我也不知道“C 数组”的其他定义。当我寻找解决方案时,我在搜索词中使用了“C 数组”,除非我是异常值,否则我相信下一个遇到相同问题的人也会这样做。我认为标签对于搜索请求来说是最重要的,它们的分类目的是其次的。 如果经常使用 C 作为低级和本机的同义词来描述的任何东西都被标记为 C,那么几乎所有与本机互操作有关的东西都会淹没 C 标记。非常糟糕的主意。 【参考方案1】:

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。 Swift 会尊重 C 的布局。

因此,我们可以传递record.name的地址, 转换为 UInt8 指针,到 字符串初始值设定项。以下代码已针对 Swift 4.2 及更高版本进行了更新:

let record = someFunctionReturningAStructRecord()
let name = withUnsafePointer(to: record.name) 
    $0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) 
        String(cString: $0)
    

注意:假定name[] 中的字节是有效的以NUL 结尾的UTF-8 序列。

对于旧版本的 Swift:

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


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

【讨论】:

是的,这行得通。我将添加 record 需要 是可变的(用 var 声明),否则 Swift 编译器会出现奇怪的错误。 @zneak:你说得对,我已将这些信息添加到答案中。 我相信这会起作用,但它是否记录了元组在内存中是连续的?使用数组,除非您调用 withUnsafeBufferPointer,否则您不会得到保证,但据我所知,尚未记录元组实现。 @NateCook:实际上,如果结构是从 C 中导入的,它保证的。我已经通过参考相应地更新了答案,并简化了代码。 @MechEthan:感谢您的通知!还有另一种解决方案,因为(从 Swift 4.2 开始)您可以获取不可变值的地址,即您可以将 withUnsafePointer(to:) 与常量值一起使用,这也避免了同时(变异)访问的问题。我已经相应地更新了代码。【参考方案2】:

关于这个主题已经有多个答案,但没有一个是简单的一行,也没有解决非空终止的问题。

假设字符串是 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 只传递一个字节的问题,你也可以传递一个指向整个变量的指针,但就个人而言,我从未体验过只传递一个字节的 swift,所以它应该是安全的。

【讨论】:

请注意,您的解决方案中有两个方面的未定义行为。首先,您只将一个字符作为 inout 表达式传递给 String 初始化程序,在此处比较 OOP 的注释:***.com/a/41599023/1187415。其次,如果字符串不是以 NULL 结尾的,那么您可能会读取未定义内存的内容。 @MartinR 那么如何将 ref 传递给整个变量呢? 应该工作,并且很可能没有运行时差异。我仍然更喜欢withMemoryRebound 而不是unsafeBitCast,它被记录为“当无法通过其他方式进行转换时,仅使用此函数将作为x 传递的实例转换为布局兼容的类型。 ... 调用这个函数破坏了 Swift 类型系统的保证;使用时要格外小心。” @MartinR 关于非空终止字符串,是的,确实,人们应该谨慎使用我的方法,在某些情况下它可能是危险的,但在其他情况下,比如我的,它是完全安全的。我有一个带有两个 char[] 的结构,然后是其他一些东西。在数组和其他东西之间有一个填充,并且结构在填充之前是 0,所以它保证它将停止读取那里并且不会读取未分配的内存。【参考方案3】:

详情

Xcode 11.2.1 (11B500)、Swift 5.1

解决方案

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://***.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()
    

用法(完整示例)

C 语言代码 (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 */

...-Bridging-Header.h

#include "Header.h"

Swift 代码

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-----------------")

输出

name: str_1
type: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
value: (72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 0)
swift string: Optional("Hello world!\0")

-----------------
name: str_2
type: (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
value: (72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
swift string: Optional("Hello world!\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")

-----------------
name: str_3
type: UnsafeMutablePointer<Int8>
value: 0x000000010e8c5d40
swift string: Optional("Hello world!")

-----------------
name: str_4
type: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
value: (72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 32, 0)
swift string: Optional("Hello world \0")

-----------------
name: str_5
type: UnsafeMutablePointer<UInt8>
value: 0x000000010e8c5d80
swift string: Optional("Hello world ")

-----------------
name: str_6
type: (UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8)
value: (72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 0)
swift string: Optional("Hello world!\0")

-----------------
name: str_7
name: str_8
type: UnsafeMutablePointer<UInt8>
value: 0x000000010e8c0ae0
swift string: Optional("")

-----------------
Optional("Hello world!\0")
Optional("Hello world!\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0")
Optional("Hello world!")
Optional("Hello world \0")
Optional("Hello world ")
Optional("Hello world!\0")
Optional("")
Optional("")

【讨论】:

【参考方案4】:

我刚刚在使用 Swift 3. (3.0.2) 时遇到了类似的问题。我试图将 CChar 数组 [CChar] 转换为 Swift 中的字符串。事实证明 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 = 可选(“abc”)

请注意,String 上的 cString 函数会导致 Optional。在创建 b 的 String.init 函数中使用时必须强制解包。而且 b 也是可选的...意味着两者都可能最终为零,因此还应该使用错误检查。

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

这是我想出的一个解决方案,它使用反射将元组实际转换为 [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】:

您实际上可以使用 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")

【讨论】:

看起来不错(不知道可变元组语法),尽管在 真实案例 中,元组有 32 个元素,我可以看到自己需要它更大的数组(如 128 个元素)。这将导致烦人的大类型注释。你有没有办法让它独立于元素的数量? let (int8s: Int8...) 是什么构造? let (name : type) = explet name : type = expr 大体一样吗? @zneak 在这种情况下,您需要内联,因为使用函数您必须输入正确数量的元素以匹配每个特定的元组。我已经修改了我的答案以显示您可以做什么。 @MartinR 它的名字是元组分解。您可以使用此技术以有趣的方式提取内容,尝试:let (_, second, _, fourth, theRest: Int...) = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 从 Xcode 7b1 beta 中的 Swift 2 开始,这不再有效。【参考方案8】:

我也有兴趣为自己的目的解决这个问题,所以我添加了一个新功能:

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 = 测试

【讨论】:

【参考方案9】:

试试这个:

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

【讨论】:

C 字符串不是UnsafePointer&lt;UInt8&gt;,它是8 个Int8 元素的元组,所以这个方法不能解决我的问题。此外,String 类有一个 init?(UTF8String: UnsafePointer&lt;CChar&gt;) 可失败初始化器,它可以完全做到这一点。 在我的应用程序中,我需要记录当前的 OpenGL 版本以确保像素格式设置正确。我用 let glVersionCString:UnsafePointer = glGetString(GLenum(GL_VERSION)) 来做到这一点。编译器不允许我将 glGetString 的返回值转换为 UnsafePointer,因此我不能使用 Swift String 初始化程序。这就是这个功能的原因。 我并不质疑您的功能在您的特定用例中的有用性,但它不适用于此处。这对您有用,因为 glGetString 返回一个指针。我处理的struct 有一个数组字段,它与指针字段有很大不同。正如我所说,Swift 看到的字段类型是(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8),而不是UnsafePointer&lt;UInt8&gt;

以上是关于将 C 字符数组转换为字符串的主要内容,如果未能解决你的问题,请参考以下文章

将 C 字符数组转换为字符串

标准C语言中如何将字符串中的内容转换为字符数组?

怎么将字符串转换为char数组 java

在c语言中怎么把字符数组转换为字符串

将C的char数组转换为C++的字符串

c语言中,如何将字符串数组中的52提取出来,并转换为int类型