Is it possible to generate a static or dynamic Swift library called by C code?

Here is my small Swift and C project:

  • Package.swift
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "TestApp",
    platforms: [.macOS(.v14), .iOS(.v13), .watchOS(.v6)],
    products: [
        .executable(name: "MyApp", targets: ["MyApp"]),
        .library(name: "MySwiftLib", type: .static, targets: ["MySwiftLib"])
    ],
    targets: [
        .executableTarget(name: "MyApp",
            cSettings: [
                .headerSearchPath("../../.build/x86_64-apple-macosx/debug/MySwiftLib.build")
            ]/*,
            linkerSettings: [
                .unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "../../.build/x86_64-apple-macosx/debug/"]),
                .linkedLibrary("MySwiftLib")
            ]*/
        ),
        .target(name: "MySwiftLib")
    ]
)
  • Sources/MySwiftLib/MySwiftLib.swift
@_cdecl("mySwiftFunc")
public func mySwiftFunc() {
    print("Hello from Swift")
}
  • Sources/MyApp/main.c
#include <stdio.h>

#include "MySwiftLib-Swift.h"

void main() {
    puts("Hello World from C!");
    mySwiftFunc();
}
  • First step: Building the static or dynamic library is not an issue: swift build --product MySwiftLib
    It produces ./.build/x86_64-apple-macosx/debug/libMySwiftLib.a and ./.build/x86_64-apple-macosx/debug/MySwiftLib.build/MySwiftLib-Swift.h when building a static library - and ./.build/x86_64-apple-macosx/debug/libMySwiftLib.dylib and ./.build/x86_64-apple-macosx/debug/MySwiftLib.build/MySwiftLib-Swift.h when building a dynamic library.

  • The second step is to build the C app. I use swift run and it seems to require objc:

/Users/olivier/dev/test_swift_lib/.build/x86_64-apple-macosx/debug/MySwiftLib.build/module.modulemap:1:8: error: module 'MySwiftLib' requires feature 'objc'
module MySwiftLib {
       ^
/Users/olivier/dev/test_swift_lib/Sources/MyApp/main.c:3:10: note: submodule of top-level module 'MySwiftLib' implicitly imported here
#include "MySwiftLib-Swift.h"
         ^
/Users/olivier/dev/test_swift_lib/Sources/MyApp/main.c:7:5: error: call to undeclared function 'mySwiftFunc'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
    mySwiftFunc();
    ^
2 errors generated.
[0/2] Compiling MyApp main.c

I have just tried to explicitly specify interoperabilityMode(.C) in Package.swift but module.modulemap still specifies requires objc:

// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "TestApp",
    platforms: [.macOS(.v14), .iOS(.v13), .watchOS(.v6)],
    products: [
        .executable(name: "MyApp", targets: ["MyApp"]),
        .library(name: "MySwiftLib", type: .static, targets: ["MySwiftLib"])
    ],
    targets: [
        (...)
        .target(name: "MySwiftLib",
            swiftSettings: [
                .interoperabilityMode(.C)
            ]
        )
    ]
)

Would you mind filing a bug report on the SwiftPM repository with a self-contained reproduction example and details about your system like Xcode and Swift versions that you're using?

Thanks @Max_Desiatov for the feedback. Here is the bug: Failed to generate a static or dynamic Swift library called by C code · Issue #7257 · apple/swift-package-manager · GitHub

1 Like

Isn’t the fundamental issue here that Swift does not have bidirectional C interop in the way it has for Objective-C and C++?

As I understand it:

  • Swift functions can't generally be called from C because they don't follow the C calling convention. There is @_cdecl, but that's not an official feature.
  • The "C header" the Swift compiler can generate for a Swift module is in fact always an Objective-C header because only @objc constructs (classes/methods/properties) can be called anyway.

Now that we have bidirectional C++ interop, it would be nice to have the same for plain C.

1 Like

It is not yet official, but the implementation of @_cdecl with regards to generated bridging headers should, to my knowledge, be mostly complete. If you aren't in fact using any Objective-C specific bridging features then the generated header ought to be usable as a plain C header, and I'd call it a bug if it's not.

3 Likes

Ah, I didn't know that. Thanks!

The main missing thing I remember was that the compiler won’t help you stick to the C subset. And Bool comes out as BOOL rather than Boolean. But yeah.

2 Likes

I mean, BOOL is probably more correct than Boolean, if only because on 64-bit iPhones and iWatch it actually is the C bool type instead of a char.

>< I meant boolean and got attacked by autocorrect, forgetting that the C type is bool anyway. Too much Java for work!

2 Likes