Description
Problem Context
As noted in multiple issues adding Swift support has been a recurring theme over the last few year's Xcode releases. The lack of direct support has lead customers to creating inventive solutions.
Despite the 'simple' problem statement: "Let us call Swift code from C#", this is actually a very difficult problem to solve for many reasons.
The three most intractable of these are:
- Swift APIs that are not marked with
@objc
can not be called with the traditional objc_msgSend nor p/invoke. The calling convention is actually not representable in C, as "self" must be passed in a a specific register by the calling conventions. In addition, Swift exceptions are stored in a register and thus not accessible either from C or p/invokes. - The Swift type system is significantly different that the one in C#, with some types not expressible in C# without significant compile and run-time support,
- Swift uses an automatic reference counting (ARC) system for memory management that can be challenging for manual bindings to correctly interoperate with.
A full solution to this problem would require a tool such as Binding Tools for Swift to be complete, which still requires a significant amount of work.
However, there are many cases where a full API binding is not required, a "thin slice" written by hand will suffice.
Prototype
The prototype is a msbuild targets file that you can leverage to compile Swift into your application along with patterns needed to successfully call it.
- Copy CompileSwift.targets next to your csproj
- Add
<Import Project="$(MSBuildThisFileDirectory)CompileSwift.targets" />
to the end of your csproj, but inside the final</Project>
tag, like this - Create a file with a Swift extension and inside it define a free function you wish to invoke that takes as arguments and returns nothing or simple types such as integers:
public func ReloadWidgets () {
WidgetCenter.shared.reloadAllTimelines()
}
- Decorate your functions with the
@_cdecl
attribute to declare that it should be invocable from C-like context, such as p/invoke:
@_cdecl("ReloadWidgets")
public func ReloadWidgets () {
- Add one or more p/invokes in your C# project, that align with the Swift you wrote in step three:
[DllImport ("__Internal", EntryPoint = "ReloadWidgets")]
public extern static int ReloadWidgets ();
Be aware that your EntryPoint, the return value, and any arguments must match Swift exactly or undefined behavior will occur.
-
Call your p/invoke from C# code
-
Test things out and report back.
Known Limitations
- No support for linking in non-system Swift libraries
So far all of the usages tests have been system Swift libraries. Likely this can be accomplished by adding linker flags:
<_MainLinkerFlags Include="-L/usr/lib/swift" />
adding any swift static libraries:
<_XamarinMainLibraries Include="@(SwiftObjectFiles)"/>
and copying in other libraries into your bundle build step extension.
- No IDE support - All of these steps must be done by hand, with a text editor
- No support for complex types, such as Strings, Structs, or Enums. Future partial support may be possible, but given the type system differences it will be limited at best.
- Manual creation of both sides of the interp bridge (Swift & C#).
Feedback
Development on this feature is in progress, and despite the limitations and required manual steps we feel that it may be useful for some use cases, specially for those who have been blocked and unable to progress on integration with some features, such as WidgetKit.
The team would appreciate any feedback on use of the prototype target, both successful and unsuccessful, and thoughts on what the most difficult steps were in testing it out.