Create C String Array from Swift

Hey everyone!!! Ok quick backstory, I am using vulkan with swift and ran into an issue passing an array of strings from swift into the struct VkInstanceCreateInfo. So I got a stupid question about how to create a pointer to an array of enabledLayerCount null-terminated UTF-8 strings. Basically, I have an array of strings in swift that contains validation layers that are needed for vulkan to log things to the console.

var enabledLayers = ["VK_LAYER_LUNARG_standard_validation"]

As of right now, only one layer exists but more will be added later for better debugging. I need to convert this Array of strings to this type for the C API:

UnsafePointer<UnsafePointer<Int8>?>!

The vulkan spec states the following about the type that needs to be passed in: ppEnabledLayerNames is a pointer to an array of enabledLayerCount null-terminated UTF-8 strings containing the names of layers to enable for the created instance. See the Layers section for further details.

How would I go about creating this C array of UTF-8 Strings from swift code to pass into the vulkan C APIs Function? Again thank you all so much for your time!

The C Type of the parameter is:

const char* const*

Hello!
I don't know if this is the best way to do it, but I created two helper extensions, one for Collection and another for String. This solution does not require Foundation.

Extension on Collection:

extension Collection {
    /// Creates an UnsafeMutableBufferPointer with enough space to hold the elements of self.
    public func unsafeCopy() -> UnsafeMutableBufferPointer<Self.Element> {
        let copy = UnsafeMutableBufferPointer<Self.Element>.allocate(capacity: self.underestimatedCount)
        _ = copy.initialize(from: self)
        return copy
    }
}

Extension on String:

extension String {
    /// Create UnsafeMutableBufferPointer holding a null-terminated UTF8 copy of the string
    public func unsafeUTF8Copy() -> UnsafeMutableBufferPointer<CChar> { self.utf8CString.unsafeCopy() }
}

Then when creating enabledLayers:

// map over the strings in array, for each string use the string extension
// to get an utf8 "unsafe" copy and get an UnsafePointer to it. Then use
// the Collection extension from above to do an "unsafe" copy of the
// array of UnsafePointers.
let enabledLayers = ["VK_LAYER_LUNARG_standard_validation"]
    .map({ UnsafePointer($0.unsafeUTF8Copy().baseAddress) }).unsafeCopy()

The type of this array will be UnsafeMutableBufferPointer<UnsafePointer<CChar>?>

When you need to use this array as an UnsafePointer<UnsafePointer<CChar>?>! you can just do UnsafePointer(enabledLayers.baseAddress) and because it is a buffer pointer you can get the count with enabledLayers.count.

Hope this helps!

1 Like

This actually did the trick!!! Thank you so much you are amazing!!! One last question, do you think I could achieve the same result without extensions? If so what would that code look like? Again thank you so much!!!! Super sorry I am kinda a swift newbe at the moment.

1 Like

No problem!
Those extensions come in handy later on if you need to copy arrays or strings and get a pointer to that storage that will only be deallocated once you call .deallocate() on them.
You can most definitely achieve the same result without extensions, although you will need more local variables and there is more code which will obfuscate the meaning of what you really want to do which is to just get an UnsafePointer<UnsafePointer<CChar>?> from a [String].

Without extensions on String or Collection:

// convert each string to a utf8 null-terminated UnsafeMutableBufferPointer<CChar>
// and then to an UnsafePointer<CChar>
let stringArray: [UnsafePointer<CChar>?] = ["VK_LAYER_LUNARG_standard_validation"]
    .map({ str in
        let cString = str.utf8CString
        let cStringCopy = UnsafeMutableBufferPointer<CChar>
            .allocate(capacity: cString.count)
        _ = cStringCopy.initialize(from: cString)
        return UnsafePointer(cStringCopy.baseAddress)
    })
// allocate enough space for all pointers in stringArray and initialize it
let stringMutableBufferPointer: UnsafeMutableBufferPointer<UnsafePointer<CChar>?> =
    .allocate(capacity: stringArray.count)
_ = stringMutableBufferPointer.initialize(from: stringArray)
// get baseAddress as an UnsafePointer<UnsafePointer<CChar>?>?
let enabledLayers = UnsafePointer(stringMutableBufferPointer.baseAddress)

Once again, this is my take on it and I don't know if there is an easier way of doing it.

Happy I could help!

2 Likes

You literally just fixed all of my problems! Thank you so much you were an amazing help!!!! LITERALLY YOU ARE THE BEST!!!!!!

2 Likes