Calling Swift Library Functions from C
Not all Swift code can directly be made available to C – or rather via C to other ecosystems that allow FFI (foreign function interfaces) from C libraries.
The key is twofold:
- Annotating what to export with
@_cdecl
, - Compiling with
-emit-library
.
The following is based on this Gist by Julian Kirsch : https://gist.github.com/HiImJulien/c79f07a8a619431b88ea33cca51de787
Export Swift Function
Export Swift code as callable from C, enforcing C calling conventions with the @_cdecl
annotation:[#20250424cdecl][]
// myfunc.swift
@_cdecl("say_hello")
public func sayHello() {
print("Hello, World!")
}
Then emit a libmyfunc.dylib
(macOS) or libmyfunc.so
(Linux):
$ swiftc myfunc.swift -emit-library
Calling Your Function from C
Call it from C:
// main.c
// Forward declaration in lieu of a header file
void say_hello();
int main() {
say_hello();
return 0;
}
Then compile the C file in isolation (just clang main.c
won’t work), and link it with the lib:
$ clang -c main.c -o main.o
$ clang libmyfunc.dylib main.o
$ ./a.out
Hello, World!
Limitations
@_cdecl
is underscored; it’s not designed to be used in production. Of course it is, because it’s around since forever. People pitch making this public here and here.- Return values apparently are always autoreleased.
- No structs. You can only apply
@_cdecl
on functions. Structs need to come from C. - You can generate header files, but boy are they long. Use
swiftc myfunc.swift -emit-library -emit-clang-header-path MySwiftModule-Swift.h -cxx-interoperability-mode=default
and includeMySwiftModule-Swift.h
. The C++ interoperability mode is, well, for C++. But without it, you get Objective-C header files only. - Could be my ignorance, but I expected
-emit-object
to produce an.o
file for the function only, but it also includes a_main
symbol (as if you want to run this), so you can’t combine it with the C program that also has a_main
entry point.