Преобразование массива C char в строку
У меня есть программа Swift, которая взаимодействует с библиотекой C. Эта библиотека C возвращает структуру с массивом char[]
внутри, например:
struct record
{
char name[8];
};
Определение корректно импортируется в Swift. Однако поле интерпретируется как набор из 8 Int8
элементов (набранных (Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
), которые я понятия не имею, как преобразовать в String
со Swift.
Нет инициализатора String
, который принимает кортеж Int8
, и не представляется возможным получить указатель на первый элемент кортежа (поскольку типы могут быть гетерогенными, что не удивительно).
Прямо сейчас, моя лучшая идея - создать крошечную функцию C, которая принимает указатель на саму структуру и возвращает name
как указатель char*
вместо массива, и пойдет с этим.
Есть ли, однако, чистый способ Swift для этого?
Ответы
Ответ 1
Массив C char name[8]
импортируется в Swift как кортеж:
(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
Адрес name
совпадает с адресом name[0]
и
Swift сохраняет структуру памяти структур, импортированных из C, так как
подтвержденный инженером Apple Джо Гроффом:
... Вы можете оставить структуру, определенную на C, и импортировать ее в Swift. Swift будет уважать макет C.
Как следствие, мы можем передать адрес record.name
,
преобразован в указатель UInt8
, чтобы
Инициализатор строки:
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[]
являются допустимой последовательностью UTF-8 с NUL-завершением.
Ответ 2
Фактически вы можете собрать кортеж в массив с помощью синтаксиса синтаксических переменных 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")
Ответ 3
Я также заинтересован в том, чтобы это сделать для моих собственных целей, поэтому я добавил новую функцию:
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
Ответ 4
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
}
Ответ 5
Я только что испытал подобную проблему, используя Swift 3. (3.0.2). Я пытался преобразовать массив CChar, [CChar] в строку в Swift. Оказывается, Swift 3 имеет инициализатор строки, который примет 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" )
Обратите внимание, что функция cString в String приводит к необязательной. Он должен быть принудительно развернут при использовании в функции String.init, создающей b. И b также является необязательным... что означает, что оба могут быть равны нулю, поэтому также следует использовать проверку ошибок.
Ответ 6
Попробуйте следующее:
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
}
Ответ 7
Здесь появилось решение, в котором используется отражение, чтобы фактически преобразовать кортеж в [Int8] (см. Любой способ перебора кортежа в swift?) и затем преобразует его в строку, используя методыCString...().
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
}
}