1

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. I hit an obstacle when attempting to read one of the structs returned by the C library in Swift.

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. After a long time of trial and error using nested withUnsafePointer calls, I eventually began searching for solutions to iterating a Tuple in 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. 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)) }.

To be clear, I'm looking for the fastest way to read these C strings in Swift, even if that involves using Reflection.

Nicklas Jensen
  • 1,424
  • 12
  • 19

2 Answers2

2

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. withUnsafePointer(&config.names) calls the closure with $0 as a pointer to the start of that memory location, and

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

is a pointer to the start of the idx-th subarray. The above code requires that each subarray contains a NUL-terminated UTF-8 string.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Thanks, since I need it for several struct / C string array combinations, I modified it to be generic by passing in the tuple of the C string array, the count (number of elements) and length (of a string in the array) as parameters, instead of the struct. – Nicklas Jensen Feb 05 '16 at 07:12
0

The solution suggested by Martin R looks good to me and, as far as I can see from my limited testing, does work. However, as Martin pointed out, it requires that the strings be NUL-terminated 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. Which of these approaches you choose depends on what specifically you are doing with RequestConfiguration in your app. If you are not comfortable programming in C, then a pure Swift approach, like the one suggested by Martin, might be a better choice.

For the purposes of this discussion, we will assume that the 3rd party C library has the following function for retrieving 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:

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. You can then do something like this in 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.

Approach 2: You just need to be able to get the name at a given position. In this case the RequestConfiguration type does not even need to be visible to Swift. You can write a helper C function like this:

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

and use it in Swift as follows:

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

This will print the name at position 2 (counting from 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.

Again, with these 2 approaches it is assumed the strings are NUL-terminated 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. 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.

Anatoli P
  • 4,791
  • 1
  • 18
  • 22
  • The problem with this solution, which I didn't explain in my question, is that in my case, I have several of these structs with string arrays in them, so I really wouldn't like having to implement a C function for each struct / field combination. – Nicklas Jensen Feb 05 '16 at 07:10