Best way to call a Swift function from C?

Hi all,

What would you recommend is the best way to call a Swift function from C? This would also allow Swift functions to be called from other languages that support C FFI.

My understanding is that currently the only way is to call a mangled function name directly, but maybe there is a similar thing to

#[no_mangle]
pub extern "C" fn double(x: i32) -> i32 {
    x * 2
}

in Rust or extern "C" in C++ that turns off mangling?

3 Likes

There's no official way yet. Aside from name mangling, Swift functions use a different calling convention from C. Unofficially, if you're willing to deal with more than the usual amount of code breakage and compiler bugs, there's an unofficial attribute @_cdecl that does this:

@_cdecl("mymodule_foo")
func foo(x: Int) -> Int {
  return x * 2
}

which you can then call from C:

#include <stdint.h>

intptr_t mymodule_foo(intptr_t);

intptr_t invoke_foo(intptr_t x) {
  return mymodule_foo(x);
}
6 Likes

While there’s no official way to call a Swift function directly from C, you can set up a function pointer that’s callable from C. Whether that helps you depends on your exact circumstances. For example, on Apple platforms it’s super useful for integrating with C APIs that use C callbacks (things like CFRunLoop observers).

Pasted in a below is an example sans anything Apple-ish.

Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"

// main.swift

private func printGreeting(modifier: UnsafePointer<CChar>) {
    print("Hello \(String(cString: modifier))World!")
}

var callbacks = SomeCLibCallbacks(
    printGreeting: { (modifier) in
        printGreeting(modifier: modifier)
    }
)
SomeCLibSetup(&callbacks)

SomeCLibTest()

// SomeCLib.h

#ifndef SomeCLib_h
#define SomeCLib_h

struct SomeCLibCallbacks {
    void (* _Nonnull printGreeting)(const char * _Nonnull modifier);
};
typedef struct SomeCLibCallbacks SomeCLibCallbacks;

extern void SomeCLibSetup(const SomeCLibCallbacks * _Nonnull callbacks);

extern void SomeCLibTest(void);

#endif

// SomeCLib.c

#include "SomeCLib.h"

static SomeCLibCallbacks sCallbacks;

extern void SomeCLibSetup(const SomeCLibCallbacks * callbacks) {
    sCallbacks = *callbacks;
}

extern void SomeCLibTest(void) {
    sCallbacks.printGreeting("Cruel ");
}
8 Likes

As a little late BTW: mod_swift and PL/Swift are examples for how this can be applied in the real world.

4 Likes

Is there any intention to convert @_cdecl into a public attribute? I think its use is extended and useful enough to convert it to something similar to @objc:

// Exposed to C ABI as `void printInfo()`
@c func printInfo() { … }

// Exposed to C ABI as `void printInfoWithInput(char * input, int count)`
@c(printInfoWithInput)
func printInfo(from input: String, count: Int) { … }

// Exposed to C ABI as `char * getInfo(char * input, int32_t count)`
@c func getInfo(from input: String, count: Int32) -> String { … }

I think we do want to do this, but it needs a formal proposal and some more implementation work to check that the arguments are actually valid for C. For instance, you wrote this:

// Exposed to C ABI as `void printInfoWithInput(char * input, int count)`
@c(printInfoWithInput)
func printInfo(from input: String, count: Int) { … }

But there's no way to go from a char * to a String without taking the count into account (unless you want to guarantee that input is also null-terminated). This also isn't compatible with the behavior of @objc, which translates String to NSString *.

The other big question is whether arbitrary other Objective-C types or types bridgeable to Objective-C are allowed, since they aren't cross-platform.

@cdecl func getOpaqueThing() -> Any
// `id getOpaqueThing(void)`, or an error?

@cdecl func compute(input: Foundation.Data) -> Int
// `intptr_t computeWithInput(NSData *input)`, or an error?
// Also, 'intptr_t' or 'NSInteger'? 'long' unfortunately wouldn't work on Windows.

I imagine different people will have different opinions on this. One way out would be to allow both @objc and @cdecl to be present on a function, and for them to enforce different rules. (We'd probably also want to consider allowing @cdecl on enums in this case too, but then again standard C doesn't have enums with underlying types.)

I do think @c is a little too short to be a good attribute name, but I guess @cdecl no longer fits our naming convention.

6 Likes

Where does convention(c) fit into this?

1 Like

That is used for calling C code from swift, like when a swift closure is really just a wrapper around a C function.

For example, when using glob(3) with the GLOB_ALTDIRFUNC flag, you could set the pglob->gl_stat variable using the following type:

public var stat: (@convention(c) (UnsafePointer<CChar>?, UnsafeMutablePointer<stat>?) -> Int32)

The gl_stat variable is a function with the C signature: int stat(const char * pathname, struct stat *buf)

@convention is orthogonal, since it affects the type of function values. In Swift we change conventions automatically when references to functions or methods are taken as closure values. The @objc attribute, and this proposed @c attribute, affect how a declaration is exported at the ABI level.

5 Likes

Yeah, in my example the idea was that input should be a null-terminated char * so under-the-hood the C-to-Swift interop would use something as String.init(cString:) to convert it, being count a random independent parameter.

If you want to use the length you could always do it manually:

// Exposed to C ABI as `void printInfo(char * input, int inputLenght)`
@c func printInfo(from input: UnsafePointer<CChar>, inputLenght: Int) { … }

Also I think that @c (or whatever is named) should bridge to pure-C types (no id, char * instead NSString) so using both attributes on a function would generate two different entry points.

In a way, the @c naming could blend beautifully in a future with more language-interop attributes available :slight_smile::

  • @c
  • @cpp
  • @objc
  • @csharp

Or maybe should we go with a new @interop attribute?

// Exposed to C ABI as `void printInfoWithInput(char * input)`
// Exposed to Objective-C ABI as `void printInfo(NSString * input)`
@interop(c, name: printInfoWithInput) @interop(objc) 
func printInfo(from input: String) { … }
6 Likes

I'm facing the same problem. Trying your suggestion gives an error from Xcode (10.1):

@_cdecl can only be applied to global functions

As I understand the above thread served as a discussion of implementation this mechanism into the Swift language, right?

Or is there a finalized method yet to do this? I'm getting compilation errors on all implementation examples I tried yet.

Thanks for the pointer. A bit lengthy that talk but interesting nonetheless. Regarding the topic: I still don't have a working example. OTOH I'm rethinking my program architecture whether I really need calling a Swift function from C :slight_smile:

But interest for the formal solution of the above is still there.
What I find weird so to say with Swift, that it is a chimera these days. Examples you find can be a year old and no longer work. And you gotta collect your knowledge tediously. Would I buy a book on Swift 4 in February it will surely be outdated.

--
Christoph

I found a blatantly simple solution here

1 Like

my 2 cents for whom want a working minimal sample.

1 Like

Hi @Joe_Groff @jrose I'm pinging you here because you seem well versed with the ins and outs of calling Swift from C.

We were relying on @_cdecl to call Swift code from Python, we also use it to call Swift code from the JNI on Android. Yesterday we updated to Swift 5.2 and it seems our @_cdecl public func xyz Swift functions have disappeared from the symbol table and can no longer be called.

Is there still an unofficial or official way of calling Swift from C? The workaround @eskimo mentioned (which is very clever, by the way, thanks for the insight!) might prove difficult to implement for our use cases.

The C entry point for a public function should also be public. Have you filed a bug?

Not yet. Wasn't sure whether this was a bug given it's "underscored" behaviour- thought maybe there's a new way (but couldn't see it in release notes). Should I file a bug on Jira or a radar somewhere on Apple?

Either one is great. If you don't have any secrets in the bug report you can't share publicly, we would prefer Jira though.

1 Like

there’s an unofficial attribute @_cdecl that does this:

I'm noticing that the test target can't find mymodule_foo when linking tests. Is there a workaround for that? Should I create a module map? I have a C module already for the file where I've placed your example (which swift calls into successfully), changing the "mymodule_foo" to the same name as the module that its in doesn't seem to help. Thoughts?

Is this problem now fixed? I consider moving my project from Objective-C++ to Swift.