C Strings and allocations

Writing a wrapper for Vulkan, I have the following struct which may accepts two strings:

typedef struct VkApplicationInfo {
    VkStructureType    sType;
    const void*        pNext;
    const char*        pApplicationName;
    uint32_t           applicationVersion;
    const char*        pEngineName;
    uint32_t           engineVersion;
    uint32_t           apiVersion;
} VkApplicationInfo;

The problem in this case is that I have String? and I have multiple ones, so withCString is not an option for me (apparently)

What I'd like to have is the following:

public extension String {

    var ptr: UnsafePointer<Int8> {
        let count = utf8CString.count
        let result: UnsafeMutableBufferPointer<Int8> = UnsafeMutableBufferPointer<Int8>.allocate(capacity: count)
        result.initialize(from: utf8CString)
        return UnsafePointer<Int8>(result.baseAddress!)
    }
}

So that I can

extension VkApplicationInfo {

    init(next: UnsafeRawPointer? = nil,
         applicationName: String? = nil,
         applicationVersion: UInt32 = 0,
         engineName: String? = nil,
         engineVersion: UInt32 = 0,
         apiVersion: UInt32 = 0) {

        self.init(
                sType: VK_STRUCTURE_TYPE_APPLICATION_INFO,
                pNext: nil,
                pApplicationName: applicationName?.ptr,
                applicationVersion: applicationVersion,
                pEngineName: engineName?.ptr,
                engineVersion: engineVersion,
                apiVersion: apiVersion)
    }
    ...
}

The doubts I have are:

  • where are the buffers allocated? Heap or stack?
  • shall I manually free them after or they'll be taken care by ARC?
  • is there a better way to do this, or an already existing function/solution in the stdlib?

Since you call UnsafeMutableBufferPointer<Int8>.allocate, you're allocating memory on the heap. You are also responsible for deallocating that memory via UnsafeMutableBufferPointer<Int8>.deallocate (I can't remember if we guarantee malloc/free-compatibility, but I think we do?). So like it is, you're leaking memory.

The better/correct way to do this depends on the API you are wrapping, and what kind of API you want to expose. Since Strings have lifetime considerations, I would recommend creating your own ApplicationInfo struct which stores that data in Swift-native, ARC-ed types like String, and using a scoped function to call in to C-land:

(I'm pretending these are not optional to make the example clearer)

struct ApplicationInfo {
  let name: String
  let engineName: String
  // ...

  func withVkApplicationInfo<R>(_ perform: (VkApplicationInfo) throws -> R) rethrows -> R {
    name.withCString { namePtr in
      engineName.withCString { engineNamePtr in
        let cVal = VkApplicationInfo(..., namePtr, engineNamePtr, ...)
        try perform(cVal)
      }
    }
  }
}

let myApp = ApplicationInfo(name: "Hello, world", engineName: "MyEngine")
myApp.withVkApplicationInfo { vkApp in
  // Do something with your C struct.
  // String lifetime is handled automatically.
}

This gives Swift users all of our nice String APIs, and (IIRC) getting a C string does not perform any copying, because Swift Strings are already null-terminated UTF8, so it should be pretty cheap to bridge the structure over in to C when you need to.

3 Likes

Having experience with the lwjgl vulkan binding, allocating on the stack has worked pretty well.

Is it possible to do the same in Swift?

Swift can promote heap allocations to the stack as an optimization (involving either UnsafeMutablePointer or ManagedBuffer), but there's no way today to force a stack allocation.*

* …except by writing a C function with a callback.

1 Like

That was discussed here: UnsafeMutablePointer allocation compatibility with C malloc/free. As I understand it, UnsafeMutablePointer allocation is malloc/free compatible on all platforms except Windows, but it is not guaranteed.

1 Like

Thanks, that's what I thought; you need to deallocate it with UnsafeMutableBufferPointer<Int8>.deallocate.

Optionals are a true game changer here. How would you re-write the example with them?

Also, how is written withCString under the hood? Because I'd like to write my own, if possible (for multiple optional strings)

Using a helper function:

func withCStringOrNullPointer<R>(_ s: String?, _ perform: (UnsafePointer<Int8>?) throws -> R) rethrows -> R {
  if let val = s { return try val.withCString(perform) }
  return try perform(nil)
}

let name: String? = nil
withCStringOrNullPointer(name) { namePtr in
  // namePtr will be nil
}

withCString accesses String's internal implementation details; I don't think it's possible to duplicate it exactly.

EDIT: Corrected types. Wrote this in the browser without checking.

1 Like

one last question, does throw add any (small) overhead?

I don't think so - rethrows lets the compiler know that if the given closure doesn't throw, the overall function will never throw, so it never needs to check if registers contain an error value.

But I don't know for sure.

1 Like