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
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
}
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"
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.
@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.
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 :
@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) { … }
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
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.
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.
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?
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?