简体   繁体   English

来自导入的无符号char 2D数组的Swift String

[英]Swift String from imported unsigned char 2D array

I am using a 3rd party C library in my iOS application, which I am in the process of converting from Objective-C to Swift. 我在我的iOS应用程序中使用了第三方C库,正在将其从Objective-C转换为Swift。 I hit an obstacle when attempting to read one of the structs returned by the C library in Swift. 尝试读取Swift中C库返回的结构之一时,我遇到了一个障碍。

The struct looks similar to this: 该结构类似于以下内容:

typedef unsigned int LibUint;
typedef unsigned char LibUint8;

typedef struct RequestConfiguration_ {
    LibUint8 names[30][128];
    LibUint numberNames;
    LibUint currentName;
} RequestConfiguration;

Which is imported into Swift as a Tuple containing 30 Tuples of 128 LibUint8 values. 它作为包含30个具有128个LibUint8值的元组的元组导入到Swift中。 After a long time of trial and error using nested withUnsafePointer calls, I eventually began searching for solutions to iterating a Tuple in Swift. 在使用嵌套的withUnsafePointer调用进行了长时间的尝试和错误withUnsafePointer ,我最终开始寻找在Swift中迭代元组的解决方案。

What I ended up using is the following functions: 我最终使用的是以下功能:

/**
 * Perform iterator on every children of the type using reflection
 */
func iterateChildren<T>(reflectable: T, @noescape iterator: (String?, Any) -> Void) {
    let mirror = Mirror(reflecting: reflectable)
    for i in mirror.children {
        iterator(i.label, i.value)
    }
}

/**
 * Returns a String containing the characters within the Tuple
 */
func libUint8TupleToString<T>(tuple: T) -> String {
    var result = [CChar]()
    let mirror = Mirror(reflecting: tuple)
    for child in mirror.children {
        let char = CChar(child.value as! LibUint8)
        result.append(char)

        // Null reached, skip the rest.
        if char == 0 {
            break;
        }
    }

    // Always null terminate; faster than checking if last is null.
    result.append(CChar(0))

    return String.fromCString(result) ?? ""
}

/**
 * Returns an array of Strings by decoding characters within the Tuple
 */
func libUint8StringsInTuple<T>(tuple: T, length: Int = 0) -> [String] {
    var idx = 0
    var strings = [String]()
    iterateChildren(tuple) { (label, value) in
        guard length > 0 && idx < length else { return }

        let str = libUint8TupleToString(value)
        strings.append(str)
        idx++
    }
    return strings
}

Usage 用法

func handleConfiguration(config: RequestConfiguration) {
    // Declaration types are added for clarity
    let names: [String] = libUint8StringsInTuple(config.names, config.numberNames)
    let currentName: String = names[config.currentName]
}

My solution uses reflection to iterate the first Tuple, and reflection to iterate the second, because I was getting incorrect strings when using withUnsafePointer for the nested Tuples, which I assume is due to signage. 我的解决方案使用反射来迭代第一个元组,并使用反射来迭代第二个元组,因为当对嵌套元组使用withUnsafePointer时,我得到了不正确的字符串,我认为这是由于标牌引起的。 Surely there must be a way to read the C strings in the array, using an UnsafePointer alike withUsafePointer(&struct.cstring) { String.fromCString(UnsafePointer($0)) } . 当然,必须有一种使用UnsafePointer读取数组中C字符串的方法,就像withUsafePointer(&struct.cstring) { String.fromCString(UnsafePointer($0)) }

To be clear, I'm looking for the fastest way to read these C strings in Swift, even if that involves using Reflection. 需要明确的是,我正在寻找最快的方法来在Swift中读取这些C字符串,即使这涉及到使用反射。

Here is a possible solution: 这是一个可能的解决方案:

func handleConfiguration(var config: RequestConfiguration) {
    let numStrings = Int(config.numberNames)
    let lenStrings = sizeofValue(config.names.0)

    let names = (0 ..< numStrings).map { idx in 
        withUnsafePointer(&config.names) {
            String.fromCString(UnsafePointer<CChar>($0) + idx * lenStrings) ?? ""
        }
    }
    let currentName = names[Int(config.currentName)]

    print(names, currentName)
}

It uses the fact that 它使用的事实是

LibUint8 names[30][128];

are 30*128 contiguous bytes in memory. 内存中有30 * 128个连续字节。 withUnsafePointer(&config.names) calls the closure with $0 as a pointer to the start of that memory location, and withUnsafePointer(&config.names)使用$0作为该内存位置开始的指针调用闭包,并且

UnsafePointer<CChar>($0) + idx * lenStrings

is a pointer to the start of the idx-th subarray. 是指向第idx个子数组的开始的指针。 The above code requires that each subarray contains a NUL-terminated UTF-8 string. 上面的代码要求每个子数组都包含一个NUL终止的UTF-8字符串。

The solution suggested by Martin R looks good to me and, as far as I can see from my limited testing, does work. Martin R建议的解决方案对我来说看起来不错,并且从我有限的测试中可以看出,它确实有效。 However, as Martin pointed out, it requires that the strings be NUL-terminated UTF-8. 但是,正如Martin所指出的,它要求字符串是NUL终止的UTF-8。 Here are two more possible approaches. 这是另外两种可能的方法。 These follow the principle of handling the complexity of C data structures in C instead of dealing with it in Swift. 这些遵循的原则是处理C语言中C数据结构的复杂性,而不是在Swift中处理它。 Which of these approaches you choose depends on what specifically you are doing with RequestConfiguration in your app. 您选择哪种方法取决于您在应用程序中对RequestConfiguration的具体处理方式。 If you are not comfortable programming in C, then a pure Swift approach, like the one suggested by Martin, might be a better choice. 如果您不习惯用C进行编程,那么像Martin所建议的那样,使用纯粹的Swift方法可能是更好的选择。

For the purposes of this discussion, we will assume that the 3rd party C library has the following function for retrieving RequestConfiguration: 为了便于讨论,我们将假设第三方C库具有以下用于检索RequestConfiguration的功能:

const RequestConfiguration * getConfig();

Approach 1: Make the RequestConfiguration object available to your Swift code, but extract names from it using the following C helper function: 方法1:使RequestConfiguration对象可用于您的Swift代码,但是使用以下C帮助器函数从中提取名称:

const unsigned char * getNameFromConfig(const RequestConfiguration * rc, unsigned int nameIdx)
{
    return rc->names[nameIdx];
}

Both this function's signature and the RequestConfiguration type must be available to the Swift code via the bridging header. 该功能的签名和RequestConfiguration类型都必须通过桥接头可用于Swift代码。 You can then do something like this in Swift: 然后,您可以在Swift中执行以下操作:

var cfg : UnsafePointer<RequestConfiguration> = getConfig()

if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig(cfg, cfg.memory.currentName)))
{
    print(s)
}

This approach is nice if you need the RequestConfiguration object available to Swift in order to check the number of names in multiple places, for example. 例如,如果您需要可用于Swift的RequestConfiguration对象以检查多个位置的名称数,则此方法很好。

Approach 2: You just need to be able to get the name at a given position. 方法2:您只需要能够在给定位置获得名称。 In this case the RequestConfiguration type does not even need to be visible to Swift. 在这种情况下,RequestConfiguration类型甚至不需要对Swift可见。 You can write a helper C function like this: 您可以这样编写一个辅助C函数:

const unsigned char * getNameFromConfig1(unsigned int idx)
{
    const RequestConfiguration * p = getConfig();
    return p->names[idx];
}

and use it in Swift as follows: 并在Swift中如下使用它:

if let s = String.fromCString(UnsafePointer<CChar>(getNameFromConfig1(2)))
{
    print(s)
}

This will print the name at position 2 (counting from 0). 这将在位置2(从0开始计数)上打印名称。 Of course, with this approach you might also want to have C helpers that return the count of names as well as the current name index. 当然,使用这种方法,您可能还需要C帮助程序来返回名称计数以及当前名称索引。

Again, with these 2 approaches it is assumed the strings are NUL-terminated UTF-8. 同样,通过这两种方法,假定字符串为NUL终止的UTF-8。 There are other approaches possible, these are just examples. 还有其他可能的方法,这些只是示例。

Also please note that the above assumes that you access RequestConfiguration as read-only. 还请注意,以上假设您以只读方式访问RequestConfiguration。 If you also want to modify it and make the changes visible to the 3rd party library C code, then it's a different ballgame. 如果您还想对其进行修改并使更改对第三方库C代码可见,那么这将是另一回事。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM