将 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) = exp
和let 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<UInt8>
,它是8 个Int8
元素的元组,所以这个方法不能解决我的问题。此外,String
类有一个 init?(UTF8String: UnsafePointer<CChar>)
可失败初始化器,它可以完全做到这一点。
在我的应用程序中,我需要记录当前的 OpenGL 版本以确保像素格式设置正确。我用 let glVersionCString:UnsafePointerglGetString
返回一个指针。我处理的struct
有一个数组字段,它与指针字段有很大不同。正如我所说,Swift 看到的字段类型是(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
,而不是UnsafePointer<UInt8>
。以上是关于将 C 字符数组转换为字符串的主要内容,如果未能解决你的问题,请参考以下文章