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:

  1. Annotating what to export with @_cdecl,
  2. 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 include MySwiftModule-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.