arrays - resueltos - Convertir una matriz de caracteres C en una cadena
funciones de cadenas de caracteres en c++ (8)
Tengo un programa Swift que interopera con una biblioteca C.
Esta biblioteca C devuelve una estructura con una matriz
char[]
dentro, como esta:
struct record
{
char name[8];
};
La definición se importa correctamente en Swift.
Sin embargo, el campo se interpreta como una
tupla
de 8 elementos
Int8
(tipeados
(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
), que no tengo idea de cómo transformar en una
String
con Swift.
No hay un inicializador de
String
que acepte una tupla
Int8
, y no parece posible obtener un puntero al primer elemento de la tupla (dado que los tipos pueden ser heterogéneos, eso no es realmente sorprendente).
En este momento, mi mejor idea es crear una pequeña función C que acepte un puntero a la estructura misma y devolver el
name
como un puntero
char*
lugar de una matriz, e ir con eso.
¿Existe, sin embargo, una forma pura y rápida de hacerlo?
Detalles
- Xcode 11.2.1 (11B500), Swift 5.1
Solución
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() } }
Uso (muestra completa)
Código en lenguaje C (Encabezado.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"
Código 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-----------------") }
Salida
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("")
Acabo de experimentar un problema similar con Swift 3. (3.0.2). Intentaba convertir una Matriz de CChar, [CChar] en una Cadena en Swift. Resulta que Swift 3 tiene un inicializador de String que tomará un cString.
Ejemplo:
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)")
resultados en
a = Opcional ([97, 98, 99, 0])
b = Opcional ("abc")
Tenga en cuenta que la función cString en String da como resultado un Opcional. Debe desenvolverse a la fuerza cuando se usa en la función String.init que crea b. Y b también es Opcional ... lo que significa que ambos podrían terminar siendo nulos, por lo que también se debe usar la verificación de errores.
Aquí hay una solución que se me ocurrió que utiliza la reflexión para convertir realmente la tupla en un [Int8] (consulte ¿ Alguna forma de iterar una tupla en Swift? ), Y luego la convierte en una cadena utilizando los métodos 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
}
}
El
char name[8]
matriz C
char name[8]
se importa a Swift como una tupla:
(Int8, Int8, Int8, Int8, Int8, Int8, Int8, Int8)
La dirección del
name
es la misma que la dirección del
name[0]
, y Swift
conserva
el diseño de memoria de las estructuras importadas de C, según lo
confirma el ingeniero de Apple Joe Groff:
... Puede dejar la estructura definida en C e importarla a Swift. Swift respetará el diseño de C.
Como consecuencia, podemos pasar la dirección de
record.name
, convertida en un puntero
UInt8
, al inicializador de 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)
}
}
NOTA:
Se supone que los bytes en el
name[]
son una secuencia UTF-8 válida terminada en NUL.
En realidad, puede recopilar una tupla en una matriz utilizando la sintaxis de parámetros variables de 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")
Estoy interesado en resolver esto también para mis propios fines, así que agregué una nueva función:
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
}
Llamo a esta función con:
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)")
la salida resultante es:
testSwiftString = prueba
Prueba esto:
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
}
Swift 3. Solo usa la reflexión. Esta versión deja de construir la cadena cuando encuentra un byte nulo. Probado
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
}