Sugaring C APIs

Is the documentation (and language support) for C sugaring better these days? If so, where can I find it?

I'm trying to make GDAL accessible from Swift via SPM. My attempt so far has failed in the link stage (swift run of my little test app results in "ld: warning: Could not find or use auto-linked library 'gdal'", even though the lib is in /usr/local/lib. I tried setting DYLD_FALLBACK_LIBRARY_PATH, as neither path variable was set in my environment, but it didn't help).

But it does compile. Unfortunately, a C function like this:

const char* GDALVersionInfo(const char* pszRequest)

results in

func GDALVersionInfo(_ inS: UnsafePointer<Int8>) -> UnsafePointer<Int8>

Is there a way to make that look like

func GDALVersionInfo(_ inS: String) -> String

without writing an explicit wrapper method? I can pass a string literal in the call without issue, but I have to create a String to get one out:

let gv = String(cString: GDALVersionInfo("RELEASE_NAME"))

I know there are a ton of macros for sugaring C APIs that can be applied in the C header files, and the APINotes compiler hasn’t had a commit in over two years (that swift-clang project is archived, and searching the apple org on GitHub for "APINotesYAMLCompiler" didn't turn up anything more).

So yeah, I'm wondering what current best practices are in this regard.

C (and Python) libraries are going to remain crucial to Swift adoption for many years. I wish it weren’t so painful to use them.

I assume by sugaring you’re referring to the lldb__attribute__s you can place in the C header files to rename things for the Swift interface?

Those mostly only let change the names of things unfortunately. Other then Obj-C which has a couple powerful ones, some of which work in C like enums which can become option sets.

But I don’t believe any of those can actually change types. You’re just changing the name Swift is using.

Unfortunately you’ll need to make a wrapper to expose a different function than what the C function actually is.

I spent a couple months making this one I’m not entirely certain is was worth the effort but Im happy I did it.

1 Like

No. A Swift String cannot be validly automatically created from an UnsafePointer<Int8> in the general case because you have to answer the question of memory ownership. Who owns the returned pointer?

Some amount of cleaning up can be done using apinotes, but those are both essentially undocumented and appear to be unsupported by the Swift Package Manager, so this is not suitable.

I’m afraid the current best practices are to do this wrapping yourself. I can provide more generalised guidance (I do this a lot) but it won’t get you out of having to wrap the functions and objects yourself.

5 Likes

I believe it is also worthy of note, that you dont need to create a wrapper function, if your argument has the type of const char */UnsafePointer<CChar>. The compiler accepts Swift.String as a valid argument for const char * and creates the “bridge” for you.

(The pointer created by the bridge MUST NOT outlive the scope of the executed function!)

You can get more information in the Apple documentation. I believe, that the Swift Sema is responsible for putting the “bridges” in place and I have found, that the “bridges” are implemented at the bottom of this swift/Pointer.swift at main · apple/swift · GitHub file.

You can also pass non-const char * as demonstrated below.

var fce: @convention(c) (UnsafeMutablePointer<Int8>) -> Void = { ptr in
    print(ptr)
}

let myMessage = "🐱 and 🐶 in my 🏠 like each other!"
var cString = Array(myMessage.utf8CString)

fce(&cString)

As for the return value, you might be able to introduce some syntactic sugar.

I can't change the C code. I had hoped to be able to add metadata, like an apinotes file, to tell Swift how to treat calls. I'd love to guide it well enough that something like what happens with CGContext could occur, without writing a bunch of simple wrappers, and without excessive generated code.

I thought the change in String’s implementation to be UTF8 under the hood was supposed to make it easier to interop with C, for example.

It is. But that change has nothing to do with the problem I outlined above regarding memory ownership.

Prior to the change to UTF-8 Strings would need to encode-and-decode every time they pass data to and from C. Now they don't. That's a huge win! But it doesn't imply "and therefore char * -to-String transformations are trivial". char * is not String, and you will always be required to tell Swift how to get from what you have to what you want. No general solution exists to that problem.

1 Like

Hi there,

did you in the end succeed in building a Swift Package with the GDAL library? I have successfully built an app on Linux using SPM and GDAL. It is still early work and not really generic yet but it might give you some hints on how to use GDAL, see the project here. The app is a re-implementation of a c++ software I made for tiling raster maps. It successfully tiles jp2 maps of a certain source. I'm planning to continue developing it, also for the Mac, not sure if linking will be different there. Let me know if I can help you in any way! These geo APIs would greatly benefit from a Swift sugar coating :wink:

kind regards,

Tim

1 Like

I think the limitations are due to macOS. I ended up linking the lib directly to my Xcode project, and writing a simple wrapper in Swift. No proper module.

Terms of Service

Privacy Policy

Cookie Policy