7

In Swift 3, C function with signature const char *f() is mapped to UnsafePointer<Int8>! f() on import. It's result can be converted to a Swift string as:

let swiftString = String(cString: f())

The question is, how a NULL terminated C array of C strings can be mapped to Swift array of strings?

The original C signature:

const char **f()

Imported Swift signature:

UnsafeMutablePointer<UnsafePointer<Int8>?>! f()

Swift array of strings:

let stringArray: [String] = ???

1 Answer 1

14

There is no built-in method as far as I know. You have to iterate over the returned pointer array, converting C strings to Swift Strings, until a nil pointer is found:

if var ptr = f() {
    var strings: [String] = []
    while let s = ptr.pointee {
        strings.append(String(cString: s))
        ptr += 1
    }
    // Now p.pointee == nil.

    print(strings)
}

Remark: Swift 3 uses optional pointers for pointers that can be nil. In your case, f() returns an implicitly unwrapped optional because the header file is not "audited": The compiler does not know whether the function can return NULL or not.

Using the "nullability annotations" you can provide that information to the Swift compiler:

const char * _Nullable * _Nullable f(void);
// Imported to Swift  as
public func f() -> UnsafeMutablePointer<UnsafePointer<Int8>?>?

if the function can return NULL, and

const char * _Nullable * _Nonnull f(void);
// Imported to Swift  as
public func f() -> UnsafeMutablePointer<UnsafePointer<Int8>?>

if f() is guaranteed to return a non-NULL result.

For more information about the nullability annotations, see for example Nullability and Objective-C in the Swift blog.

Sign up to request clarification or add additional context in comments.

8 Comments

... and very-similar techniques are needed in other languages (including C++) which support a "true" String type. Each of these "other" languages maintain a separate "length" for each string-value: they don't rely on terminating null-bytes. But, they all have methods which are specifically designed to deal with them. (And, commonly, they keep a known-NULL byte at the end of the current-value which they're storing.)
... and ... "meh" ... whenever it comes to digital computers, I "never say 'never.'" If it's a pointer, it might be NULL. Any future *bug in f() that produced this return-value would blossom into a very nasty and hard-to-locate bug, wasting maybe hours from what was meant to shave microseconds. I'm a very conservative coder ...
@MikeRobinson: I did not add that remark to "shave microseconds" but to direct attention to the nullability annotations. Of course it can be discussed if the return value of an API which is documented to return a valid pointer should be checked on each invocation, or if that is considered a programming error and should crash early.
... and, Martin, I did not address that comment specifically at you. I apologize if you interpreted it otherwise, and I can surely see how you might have. I guess I was thinking not-so-much in terms of "nice, documented APIs," but rather, "code somewhere-else in this legacy application that we all somehow make our living to support." However, I clearly re-read that you undoubtedly were talking about "APIs and such." Thus, my comments may have been (in your intended context) out-of-line, and for that I do publicly apologize.
In Swift 3 I had to change ptr += 1 to ptr = ptr.advanced(by: 1)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.