xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.42k stars 507 forks source link

[Prototype] Swift Code Extensions to Xamarin applications #15315

Open chamons opened 2 years ago

chamons commented 2 years ago

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:

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.

  1. Copy CompileSwift.targets next to your csproj

  2. Add <Import Project="$(MSBuildThisFileDirectory)CompileSwift.targets" /> to the end of your csproj, but inside the final </Project> tag, like this

  3. 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() 
    }
  4. 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 ()  {
  5. 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.

  6. Call your p/invoke from C# code

  7. Test things out and report back.

Known Limitations

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.

wjk commented 2 years ago

Copy CompileSwift.targets next to your csproj

The link 404’s.

chamons commented 2 years ago

@wjk - Bah, those links were referencing my branch before landing in master. Fixed.

motoko89 commented 2 years ago

@chamons Do you have any suggestion for debugging WidgetKit extension? Is it possible to do it from VS Mac with the actual host app installed?

JeroenBer commented 2 years ago

@chamons I wanted to test it on Xamarin.iOS but I got some errors, it only works for .NET6 iOS right ?

rolfbjarne commented 2 years ago

@JeroenBer yes, this is .NET 6 only

chamons commented 2 years ago

@motoko89 - Debugging is something on my radar to look into, but I doubt VSfM will have any support for a long time.

Your best bet, off the top of my head, would be somehow connecting to the App/Extension with Xcode and trying to set breakpoints on the swift there.

But as I noted before, I have not looked into this yet at all.

Csaba8472 commented 2 years ago

I think, this is a viable solution for certain use cases like calling the widget update method and it's certainly easier than create a framework project and a binding project just to call one method.

However, if there is an initiative to create a way to use swift, I'd rather approach the problem more generally. While a lot of iOS extensions are supported by vs/.net, some of them are not (widget, watch app). Beside that there are SwiftUI frameworks with no objc support (Swift Charts) and swift frameworks (WidgetCenter) which are not bindable easily as you wrote. If there was a "shadow" (or runner is flutter's case) xcode project, one could create widget, watch app, bind swift, swiftui frameworks in that project and then use that from the main .net project. (Something like Microsoft.Maui.Platform.Channels). This approach would help at least with these things:

  1. be able to call any swift code with almost any return type from an xcode project, without slim binding. I think, Microsoft.Maui.Platform.Channels covers this.
  2. have support in csproj for the xcode Products. For example, like AdditionalAppExtensions for widgets just for watch apps.
chamons commented 2 years ago

SwiftUI frameworks with no objc support (Swift Charts)

You should be able to host those in a https://developer.apple.com/documentation/swiftui/uihostingcontroller and return that (in the future once we have a good way to expose UIView).

Something like Microsoft.Maui.Platform.Channels). This approach would help at least with these things:

Integration of this instrastructure with that is already actively being researched. This likely feels "half-finished" because it is - the team wanted to get this approach out there to unblock people before all of the research was completed.

tekmun commented 7 months ago

I copied Bridge.swift and CompileSwift.targets from the project xamarin-ios-swift-extension into Platforms/iOS directory in Maui .net8-rc.2. Then, I added in the last line of my .csproj.

Compilation no error but the app crashes on launch. This is my last step in porting widget support into maui iOS app. Any suggestion? How do I get a log on what is the cause of the crash? Thanks.

rolfbjarne commented 7 months ago

@tekmun does it happen both in the simulator and on device? What's the Application Output when it happens?

tekmun commented 7 months ago

I only tested it on a device (iPad iOS 16). I am using command line "dotnet build -t:Run -f net8.0-ios -v 4 munsblog.csproj /p:_DeviceName=" or Visual Studio Code. How do I check the Application Output? How do I get a log on the cause of the crash on the command line or VSC?

rolfbjarne commented 7 months ago

If you run from the terminal the app's output should be printed to the terminal.

What's the output if you ask for verbose logs (the -v:diag argument)?

dotnet build -v:diag -t:Run -f net8.0-ios -v 4 munsblog.csproj /p:_DeviceName=

tekmun commented 7 months ago

After a clean rebuild, the error is "AMDeviceSecureInstallApplicationBundle returned: 0xe8008015" It looks like the Bridge.swift file is not signed.

I upgraded to .net8.0.100 instead of .net8.0.100-rc.2. There is a change in the Xamarin.shared.targets file. I have to comment out lines 2291 to 2302 to prevent code signing of the .appex bundle using the main bundle certificate. The .appex bundle has already been signed in Xcode using its own certificate.

tekmun commented 7 months ago

When I include Bridge.swift, the app crashed on launch with the following error:

Application 'com.learnaholic.munsblog' terminated (with exit code '' and/or crashing signal '6). dyld[1935]: Assertion failed: (kinds[depIndex] == Loader::DependentKind::weakLink), function serialize, file PrebuiltLoader.cpp, line 1113.

rolfbjarne commented 7 months ago

Can you attach the entire crash report?

tekmun commented 7 months ago

The two lines shown above were taken from the command line output when I executed it on command line. I checked the ~/Library/Developer/Xcode/DeviceLog and ~/Library/Logs for crash report. I could not find any. Can you show me where can I get the crash report? Thanks.

rolfbjarne commented 7 months ago

Crash reports from a device can be fetched from the device using Xcode. See https://github.com/xamarin/xamarin-macios/wiki/Diagnosis#crash-reports for more info.

tekmun commented 7 months ago

Thanks for the info. I did not realize I must click on the Öpen Recent Logs" button in Xcode to download the crash report. CrashReport.zip

rolfbjarne commented 7 months ago

Can you also get a binlog (https://github.com/xamarin/xamarin-macios/wiki/Diagnosis#build-logs)?

tekmun commented 7 months ago

Command line: dotnet build -f net8.0-ios munsblog.csproj /b1:msbuild.binlog

I have commented out lines 2291 - 2302 in the Xamarin.Shared.targets file to prevent code signing of the .appex bundle using the main bundle certificate. The .appex bundle has already been signed in Xcode using its own certificate. Perhaps, this issue can also be addressed.

Archive.zip

rolfbjarne commented 7 months ago

OK, just to clarify: it's the main app that's crashing when you add the app extension (as AdditionalAppExtensions) in the project file? And the main app stops crashing if you remove the AdditionalAppExtensions in the project file?

tekmun commented 7 months ago
    <AdditionalAppExtensions Include="$(MSBuildProjectDirectory)/../applewidget/munsblog/">
      <Name>widgetExtension</Name>
      <BuildOutput Condition="'$(ComputedPlatform)' == 'iPhone'">DerivedData/munsblog/Build/Products/Debug-iphoneos</BuildOutput>
      <BuildOutput Condition="'$(ComputedPlatform)' == 'iPhoneSimulator'">DerivedData/munsblog/Build/Products/Debug-iphonesimulator</BuildOutput>
    </AdditionalAppExtensions>

Commenting out lines 2291 - 2302 allows me to run the widget written in Swift. This is a side issue.

<Import Project="$(MSBuildThisFileDirectory)Platforms/iOS/CompileSwift.targets" Condition="$(TargetFramework.Contains('-ios'))" />

The main issue is to insert Bridge.swift into the main app so that I can call WidgetCenter.shared.reloadAllTimelines(). This is causing the main app to crash now. Bridge.zip

tekmun commented 7 months ago

Could it be that the object file Bridge.o is not signed with the provisional profile?

rolfbjarne commented 7 months ago

You need to compile Bridge.swift into an object file, and then include that object file as a native reference in your project:

<ItemGroup>
    <NativeReference Include="path/to/bridge.o" Kind="Static" />
</ItemGroup>
tekmun commented 7 months ago

Adding this line into my .csproj causes a duplicate symbol error. Originally, there already exists a _LinkNativeExecutable after the CompileSwift.

Tool xcrun execution started with arguments: clang++ obj/Debug/net8.0-ios/ios-arm64/Bridge.o -mios-version-min=11.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk -arch arm64 obj/Debug/net8.0-ios/ios-arm64/nativelibraries/aot-output/arm64/System.Private.CoreLib.dll.o obj/Debug/net8.0-ios/ios-arm64/Bridge.o /usr/local/share/dotnet/packs/Microsoft.iOS.Runtime.ios-arm64/17.0.8478/runtimes/ios-arm64/native/libxamarin-dotnet-debug.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libSystem.Globalization.Native.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libSystem.IO.Compression.Native.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libSystem.Native.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libSystem.Net.Security.Native.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libSystem.Security.Cryptography.Native.Apple.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libicudata.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libicui18n.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libicuuc.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-debugger-static.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-diagnostics_tracing-static.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-hot_reload-stub-static.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-marshal-ilgen-static.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmonosgen-2.0.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-debugger-static.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-diagnostics_tracing-static.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-marshal-ilgen-static.a /usr/local/share/dotnet/packs/Microsoft.NETCore.App.Runtime.Mono.ios-arm64/8.0.0/runtimes/ios-arm64/native/libmono-component-hot_reload-stub-static.a -framework Accelerate -framework Accounts -framework AddressBook -framework AddressBookUI -framework AdSupport -framework ARKit -framework AssetsLibrary -framework AudioToolbox -framework AVFoundation -framework AVKit -framework CallKit -framework CFNetwork -framework CloudKit -framework Contacts -framework ContactsUI -framework CoreAudioKit -framework CoreBluetooth -framework CoreData -framework CoreFoundation -framework CoreGraphics -framework CoreImage -framework CoreLocation -framework CoreMedia -framework CoreMIDI -framework CoreML -framework CoreMotion -framework CoreSpotlight -framework CoreTelephony -framework CoreText -framework CoreVideo -framework DeviceCheck -framework EventKit -framework EventKitUI -framework ExternalAccessory -framework FileProvider -framework FileProviderUI -framework Foundation -framework GameController -framework GameKit -framework GameplayKit -framework GLKit -framework HealthKit -framework HealthKitUI -framework HomeKit -framework IdentityLookup -framework ImageIO -framework Intents -framework IntentsUI -framework IOSurface -framework JavaScriptCore -framework LocalAuthentication -framework MapKit -framework MediaAccessibility -framework MediaPlayer -framework MediaToolbox -framework Messages -framework MessageUI -framework Metal -framework MetalKit -framework MetalPerformanceShaders -framework MobileCoreServices -framework ModelIO -framework MultipeerConnectivity -framework NetworkExtension -framework NotificationCenter -framework OpenGLES -framework PassKit -framework PDFKit -framework Photos -framework PhotosUI -framework PushKit -framework QuartzCore -framework QuickLook -framework ReplayKit -framework SafariServices -framework SceneKit -framework Security -framework Social -framework Speech -framework SpriteKit -framework StoreKit -framework SystemConfiguration -framework Twitter -framework UIKit -framework UserNotifications -framework UserNotificationsUI -framework VideoSubscriberAccount -framework VideoToolbox -framework Vision -framework WatchConnectivity -framework WebKit -weak_framework Accessibility -weak_framework AdServices -weak_framework AppClip -weak_framework AppTrackingTransparency -weak_framework AuthenticationServices -weak_framework AutomaticAssessmentConfiguration -weak_framework AVRouting -weak_framework BackgroundAssets -weak_framework BackgroundTasks -weak_framework BusinessChat -weak_framework CarPlay -weak_framework Cinematic -weak_framework ClassKit -weak_framework CoreHaptics -weak_framework CoreLocationUI -weak_framework CoreNFC -weak_framework IdentityLookupUI -weak_framework LinkPresentation -weak_framework MediaSetup -weak_framework MetalFX -weak_framework MetalPerformanceShadersGraph -weak_framework MetricKit -weak_framework MLCompute -weak_framework NaturalLanguage -weak_framework NearbyInteraction -weak_framework Network -weak_framework OSLog -weak_framework PencilKit -weak_framework PHASE -weak_framework PushToTalk -weak_framework QuickLookThumbnailing -weak_framework ScreenTime -weak_framework SensitiveContentAnalysis -weak_framework SensorKit -weak_framework SharedWithYou -weak_framework SharedWithYouCore -weak_framework ShazamKit -weak_framework SoundAnalysis -weak_framework Symbols -weak_framework ThreadNetwork -weak_framework UniformTypeIdentifiers -weak_framework VisionKit -framework GSS -framework CFNetwork obj/Debug/net8.0-ios/ios-arm64/nativelibraries/main.arm64.o obj/Debug/net8.0-ios/ios-arm64/nativelibraries/registrar.o -o obj/Debug/net8.0-ios/ios-arm64/nativelibraries/munsblog -lcompression -dead_strip -L/usr/lib/swift -L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/iphoneos -lz -liconv -lcompression -lobjc -exported_symbols_list obj/Debug/net8.0-ios/ios-arm64/mtouch-symbols.list -Xlinker -ld_classic

Is the executable copied over into the bin folder?

rolfbjarne commented 7 months ago

@tekmun I'm getting somewhat confused now. Can you prepare a complete, self-contained test project I can use to reproduce this?

tekmun commented 7 months ago

OK. I will try to do it in these two days. Thanks.