I’ve been making some progress in incorporating SDL into my Swift app, but I want to make it better. One of the problems is that SDL defines a lot of constants with macros, and those don’t import nicely. It also uses opaque pointer types (e.g. typedef struct SDL_Window SDL_Window), which import as OpaquePointer.
Do I need to create an entire parallel class hierarchy, wrapping each of the SDL types (e.g. SDL_Window), or can I use API Notes to express these types more Swiftily, and add extensions where it’s useful?
For example, SDL_Window has a bunch of methods like SDL_CreateWindow() and SDL_GetWindowSurface(). SDL_CreateWindow() takes a few parameters, one that is best expressed as an OptionSet.
Even getting API Notes to work is confusing. I tried putting ClibSDL2.apinotes in next to the .modulemap file, with the following contents:
If you do go for the wrapping approach, then wrapping C values in Swift structs rather than classes should be more or less "zero cost", since the layout and calling convention of a single-field struct in Swift is the same as the single field. So you could write something like:
and, if everything inlines, that should boil down to similar machine code to what you'd get from using the C interface directly, with a little more type safety and ergonomics than the default C import.
API notes could work too, but it would mean learning yet another not-particularly-well-documented language of attributes and annotations, and I think you can get similar results wrapping entirely in Swift.
Yeah, the struct and its single field are "layout compatible". You normally won't have to, since constructing the struct around the wrapped property, or accessing the wrapped property, should also be no-ops at the machine level.
Right, but if I had Dictionary<Key, Something> and Key wrapped a single field String, could I then also (again unsafely) cast this to a Dictionary<String, Something>?
I still wouldn't count on it. The optimizer-blessed relationship between the struct and its single field for alias analysis doesn't extend transitively to other types.
So, I’ve delved into using this Swift SDL library, and it seems very messy. I started adding to it to help make use more straightforward, but it seems to have some awkwardness built-in (for example, it creates a class to wrap an SDL struct that is intended to be immutable, and I think can be used directly, if other parts of the API could be enhanced to accept that struct, rather than unsafe pointers to the struct).
How does Apple so nicely wrap CGContext? It's an opaque struct, so you’d think it would be UnsafePointer<CGContext>, but in Swift it’s a class with an extension declaring all the operations you can do on it.
Is this just due to API Notes? Or is there something else going on?
There’s a CF_BRIDGED_TYPE macro in <CoreGraphics/CGContext.h>, which establishes the basic mapping.
There’s also CF_IMPLICIT_BRIDGING_ENABLED in the header.
There’s a boatload of API notes. If you want to see how they work, the <CoreGraphics/CoreGraphics.apinotes> file is included in the Core Graphics’ headers directory [1].
The first point is the tricky one. Apple does not support third-party CF types.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
[1] If you have Xcode installed in the usual place, you can open /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreGraphics.framework/Versions/A/Headers/CoreGraphics.apinotes.
Figures. Can I replicate what CF_BRIDGED_TYPE and CF_IMPLICIT_BRIDGING_ENABLED do?
As an aside: I wish Apple would spend more effort in improving this aspect of using Swift outside of Apple platforms. It feels like substantial friction preventing wider adoption. Each new open-source project that works well from Swift enables that many more people to use Swift for their project. This can have an extraordinary snowball effect. I really don’t want to write non-Swift code any more, but I really do want to do things that simply can't be done as well on Apple platforms (robots and IoT and cool art things, not to mention cross-platform things). Thoroughly document API Notes, expose the bridging tools, improve Swift-C interop (e.g. make importing opaque structs nicer), etc.
OK, here’s a quick question: Do the types you care about support retain and release semantics? Looking at the SDL docs, I see SDL_CreateWindow and SDL_DestroyWindow, but nothing like SDL_Retain or SDL_Release.
If not, bridging them to Swift as classes is going to be problematic because Swift objects (and Objective-C objects, and CF objects) all require that.
As an aside: I wish Apple would spend more effort in improving this
aspect of using Swift outside of Apple platforms.
That’s not something I can help with, other than to suggest that you raise your concerns over on Swift Evolution.
Oh, right. Is there a conventional technique for wrapping these objects? I realize that's what I originally set out to do. Maybe I'll just do that from scratch. @Joe_Groff above suggests wrapping the objects in a struct, but wouldn't that break some of the semantic differences between structs and classes?
Too bad you can’t just augment an imported C object with
Is there a conventional technique for wrapping these objects?
You need to start by deciding on how ‘nice’ you want to be to your clients. What sort of wrapper are you trying to create?
A minimal wrapper that just smooths over the biggest bumps?
A full-featured wrapper that presents the API in a way that a Swift developer will feel immediately at home?
If it’s the latter, then you’d want to model things in the way that makes most sense in Swift. For example:
An SDL_Window * obviously has identity, and thus should be modelled as a Swift class.
SDL_Keycode probably should be modelled as a Swift enum with a raw value.
SDL_Colour is clearly best handled as a Swift struct.
Remember that, just because something is allocated on the heap, doesn’t mean you have to model it as a class. For example, an SDL_Palette might make sense as a value type, and if you think that’s the case then you should model it as a struct. You can do this using the standard copy-on-write technique:
Have an internal class that actually holds the SDL_Palette *.