C FFI: How to pass const char* argument

From what I have read, I should be able to pass a swift string directly and the compiler auto-converts it a const char*.

Instead, I am getting an error

 error: cannot assign value of type 'String' to type 'UnsafePointer<CChar>' (aka 'UnsafePointer<Int8>')

The C struct in question

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 line causing problems in my code

var appInfo  VkApplicationInfo()
appInfo.pApplicationName = "Hello Triangle"

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.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

While coincidentally toying with Vulkan in swift myself in the past I used these two methods:

myString.cString(using: .utf8)?.withUnsafeBufferPointer { $0.baseAddress }

and

(myString as NSString).utf8String

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 :stuck_out_tongue:

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.

The question of lifetime is crucial here.

You can do this:

func call(appName: UnsafePointer<CChar>, engineName: UnsafePointer<CChar>) {
    let appInfo = VkApplicationInfo(
        sType: 0,               // TODO: change appropriately
        pNext: nil,             // TODO: check
        pApplicationName: appName,
        applicationVersion: 0,  // TODO: change appropriately
        pEngineName: engineName,
        engineVersion: 0,       // TODO: change appropriately
        apiVersion: 0           // TODO: change appropriately
    )
    useAppInfo(appInfo)
    // do not use appInfo outside (do not escape it from here)
}

call it like so:

call(appName: "Hello Triangle", engineName: "foo")

and so long as you don't leak appInfo (or it's pointers) outside and don't use those after you returned from "call" – you'll be ok.

3 Likes

Yep. BTW, compiler only thinks it has the idea of the lifetime of passed argument in case of a function call...

// C code
const char* rememberedString;

void rememberString(const char* str) {
    rememberedString = str;
}

void drawRememberedString(void) {
    printf("%s\n", rememberedString);
}
// swift code
rememberString(swiftString)
// some time later
drawRememberedString() // CRASH! BOOM! BANG!
2 Likes

thanks. I worked around it using withCString but it was messy.

I am wondering if there is a best practice or a good guide for the general problem of calling C libs.

I also need to pass structs, mutable ones too.

Thanks, this solution looks a lot cleaner!

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).

Assuming this prototype:

const char * _Nonnull * _Nonnull glfwGetRequiredInstanceExtensions(uint32_t *);

you can call it from Swift just like that:

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:

  1. result![i]!
  2. 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.

frzi wrote:

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.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

[1] See Objective-C Memory Management for Swift Programmers.

2 Likes

rishflab wrote:

Sorry if this is an ultra basic question.

I would in no way characterise this stuff as “ultra basic” |-:

If you can point me to a resource to learn about C ffi best
practices/tips

I’m not aware of a single resource that covers all of this. A good place to start is Objective-C and C Code Customization, which has a lot of good info. Also, The Peril of the Ampersand, on DevForums, covers the most common gotcha.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

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.

I gave some examples of the various possible scenarios, and how you can handle them in Swift: Converting C array to Swift and back - Stack Overflow

3 Likes