I should be able to pass a swift string directly and the compiler
auto-converts it a const char *.
Right, but only in function calls. In this case you’re not calling a function but assigning to a property. The compiler can’t generating a C string for that because it has no idea about the lifetime of your appInfo structure.
There are various ways to solve this. The right one to choose depends on the lifetime of appInfo. My solution of last resort is to call strdup and then free that if necessary.
Now I’m well aware this isn’t the way to do this. (Don’t worry, I never used this in production code) And these methods only worked when the lifetime of the struct they were placed in didn’t outlive the (function) scope they were declared in.
I take no responsibility if things blow up when you use these
But I am also interested in what the correct and graceful way of feeding a C function/struct a String in Swift is, in the case I find myself dealing with C libraries again. Would love to hear the experts’ opinions.
Would the function wrapping technique you mentioned work for mutable structs and other value types?
let num: UInt32 = 0
var glfwExtensionCount = [num]
let glfwExtensions = glfwGetRequiredInstanceExtensions(UnsafeMutablePointer(mutating: glfwExtensionCount));
gflwGetRequiredInstanceExtensions is a C function that mutates glfwExtensionCount. How should I pass this as an arg and access this after mutating it?
Sorry if this is an ultra basic question. If you can point me to a resource to learn about C ffi best practices/tips, I should be able to figure it out.
What are you going to mutate? If that's a simple value like Int - it's easy, for char* values it's harder (e.g. if C wants to mutate the passed char* data – don't use the technique I posted above, you'd need something totally different).
var count: UInt32 = 0
glfwGetRequiredInstanceExtensions(&count)
to get the count. Then you can:
var count: UInt32 = 0
let result = glfwGetRequiredInstanceExtensions(&count)
for i in 0 ..< Int(count) {
print(String(cString: result[i]))
}
to convert from c strings to swift's.
If the API doesn't have those _Nonnulls there are two options:
result![i]!
wrap the prototype into NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END brackets, so the pointers will be treated as non null.
(I tried option 2 now and it doesn't work, don't know why).
Don't know about this maybe others will share some links. I'd try looking for similar questions (and answers) on the internet, and learn along the way.
And these methods only worked when the lifetime of the struct they
were placed in didn’t outlive the (function) scope they were declared
in.
That’s true for your second option, which leans into Objective-C’s autorelease pool [1]. Your first option just doesn’t work reliably. It’s relying on undefined behaviour and may or may not work in practice depending on how the compiler is feeling that day.
This is really the crux of it. C doesn't have a universal convention for how the lifetime of objects should work (e.g. there's the Copy rule vs Get rule, but that's only specific to CoreFoundation, and C compilers don't know about it). The typesystem doesn't give the tools to express lifetimes, either, so it's all documentation based.