来自导入的unsigned char 2D数组的Swift String

时间:2016-02-04 14:45:54

标签: ios c swift

我在iOS应用程序中使用第三方C库,我正在从Objective-C转换为Swift。尝试读取Swift中C库返回的一个结构时,我遇到了障碍。

结构看起来类似于:

typedef unsigned int LibUint;
typedef unsigned char LibUint8;

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

将其导入Swift作为包含30个元组128个LibUint8值的元组。经过长时间的试验和错误使用嵌套的withUnsafePointer调用,我最终开始寻找在Swift中迭代元组的解决方案。

我最终使用的是以下功能:

/**
 * 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
}

用法

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

我的解决方案使用反射来迭代第一个元组,并使用反射来迭代第二个元组,因为当使用withUnsafePointer作为嵌套元组时我得到了不正确的字符串,我认为这是由于标志。当然必须有一种方法来使用UnsafePointer withUsafePointer(&struct.cstring) { String.fromCString(UnsafePointer($0)) }来读取数组中的C字符串。

要清楚,我正在寻找在Swift中读取这些C字符串的最快方法,即使这涉及使用Reflection。

2 个答案:

答案 0 :(得分:2)

这是一个可能的解决方案:

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

它使用

这一事实
LibUint8 names[30][128];

在内存中是30 * 128个连续字节。 withUnsafePointer(&config.names)$0作为指向该开头的指针调用闭包 内存位置和

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

是指向idx-th子阵列开头的指针。以上代码要求 每个子数组包含一个NUL终止的UTF-8字符串。

答案 1 :(得分:0)

Martin R建议的解决方案对我来说很好,而且从我的有限测试中可以看出,确实有效。但是,正如Martin指出的那样,它要求字符串是NUL终止的UTF-8。以下是两种可能的方法。它们遵循在C中处理C数据结构的复杂性而不是在Swift中处理它的原则。您选择的这些方法中的哪一种取决于您在应用中使用RequestConfiguration的具体操作。如果你不习惯用C语言编程,那么纯粹的Swift方法,就像Martin建议的那样,可能是更好的选择。

出于本讨论的目的,我们假设第三方C库具有以下检索RequestConfiguration的功能:

const RequestConfiguration * getConfig();

方法1:使RequestConfiguration对象可用于Swift代码,但使用以下C辅助函数从中提取名称:

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

此函数的签名和RequestConfiguration类型都必须通过桥接头可用于Swift代码。然后你可以在Swift中做这样的事情:

var cfg : UnsafePointer<RequestConfiguration> = getConfig()

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

如果您需要Swift可用的RequestConfiguration对象来检查多个位置的名称数量,这种方法很不错。

方法2:您只需要能够在给定位置获取名称。在这种情况下,RequestConfiguration类型甚至不需要对Swift可见。你可以编写一个这样的辅助C函数:

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

并在Swift中使用它如下:

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

这将在位置2打印名称(从0开始计数)。当然,使用这种方法,您可能还希望让C帮助程序返回名称的数量以及当前的名称索引。

同样,使用这两种方法,假设字符串是NUL终止的UTF-8。还有其他可能的方法,这些只是示例。

另请注意,上述内容假定您以只读方式访问RequestConfiguration。如果您还想修改它并使更改对第三方库C代码可见,那么它就是一个不同的球赛。