Using C headers with the package manager

Hi,
I'm trying to use the discord sdk from this url in a Swift package:

It's a .dylib with a C header file; How can I import these? Sorry if this is obvious but I couldn't find anything :/

I tried something with module.modulemap but I'm getting this
image

I think the best way is to create a SPM package for the Discord SDK. First you need to turn the dylib+header into a framework: Create ios framework with dylib - Stack Overflow

Then you can turn the framework(s) into an xcframework. (xcodebuild create-xcframework -help should be enough, or look up a guide for this as well) -- Multiple if you have multiple platforms, like iOS, iOS simulator, macOS, etc., but it also works with just 1 platform.

Then you can create a binary SPM package that just points to the zipped xcframework.

I don't think xcframework works on linux though :(

dylibs don't work on Linux either, you want the so files for that

Apparently there is an artifactbundle option since 5.6 but it only works with executables yet, not libraries: swift-evolution/0305-swiftpm-binary-target-improvements.md at main · apple/swift-evolution · GitHub

I'm not familiar with linux development, thanks, I downloaded the so
still, I don't know how to use it with Swift

huh so it's not possible to import a library at all?
I have to use C?
hm :confused:

If it's not possible, how does Swift access system libraries?

Swift on non-Apple platforms is pretty much just a sideproject.

One thing I remembered is that SourceKitten (that SwiftLint uses/used) does this, it seems like they just generate Swift code from the header files and library paths: SourceKitten/library_wrapper_SourceKit.swift at main · jpsim/SourceKitten · GitHub

The macOS path is the path to the dylib (it's just renamed to not have the extension when wrapped in a framework). You can use lipo to merge the arm64 and x64 dylibs for macOS.

You need to use ‘system library’ target Apple Developer Documentation

I have used it for example here for SDL GitHub - mikolasstuchlik/GameTest: Playground for my exploration of SDL2 from Swift

Does it fit your purpose? If not, you can pass commands directly to the compiler to link any shared or static library.

1 Like

Oh, I tried the system library target, and nothing gave me errors, except doing import DiscordSDK in my main file; that gave me some weird obj-c related error

I will try it again

Okay thank you, it seems to be working now! :slight_smile:
I don't know why but I'm not complaining haha
I must have been doing something slightly wrong somewhere

Hmm, this is really painful though, the Swift type system is really getting in the way
it's doing weird things like assuming C strings are tuples, optional values as not optional etc
Is there a way to correct it somehow?

No, that‘s the way the interoperability works. What most people do is write a higher level glue library in Swift that builds on the automatically generated API.

I mean yeah I understand that, but I'm not sure how I'm supposed to do that when the language is so different. C is expecting a fixed size array, there is no such concept in Swift

The main aim of Swift is to be safe. Therefore Swift requires a lot code when working with unsafe lanugages - such as C. This is almost a design choice - the explicit use of symbols with unsafe word in it makes it clear, that we’re leaving the boundries of the safe world of Swift.

I recommend to you following documents:

The last document I have mentioned provides the answer for your other question regarding touples. Fixed-size array is interpreted as a Touple, because Touples follow the same layouting rules. Yes, there is no such concept as “fixed size array” in Swift, but Touple is both fixed size and contiguous.

Remeber, that Swift lacks the ability to use any C macro that does not represent a value directly. If a said macro accepts arguments or expands into some code etc. you can not use it from Swift and the only workaround known to me is wrapping it from C code to a C function.

Swift is also able to “pass some types directly to C” - for example, you may pass Swift.String directly to const char * argument. Or some inout variables. The Swift compiler will generate a code that will ensure the binary compatibility.

However in most cases, Swift complier lacks a lot of information about the C code and therefore is not able to assume anything. Therefore all pointers apper to be optional etc.

2 Likes

On optional values being non optional or vice versa: there are macros which you can use to control that either on a block level (NS_ASSUME_NONNULL_BEGIN / NS_ASSUME_NONNULL_END) or an individual declaration level (_Nullable / __nullable / _Nonnull / __nonnull)

Also consider introducing Obj-C shim class in between swift and C - much easier this way:

// MARK: ObjcCode.m
void objc_foo(NSString* __nonnull string) {
    c_foo(string.utf8String);
}
NSString* __nonnull objc_bar() {
    return [NSString stringWithUTF8String: c_bar()];
}

Then your swift code would be simply:

let string: String = objc_foo()
objc_bar("swift string")
1 Like

Thank you, this is really useful information

Yeah the tuples are really the only major annoyance

Is it possible to create a typealias forcing something to be a String shorter than a certain length?

I am not familiar with obj-c unfortunately

I found #error() but I'm not sure how to use it with a type alias

To my knowledge, there is no way of doing this statically. You would need to check it at runtime.

1 Like