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.44k stars 507 forks source link

iOS binding library causes app crash when Swift wrapper library returns String from a function #20919

Closed kwende closed 1 month ago

kwende commented 1 month ago

Steps to Reproduce

Please note that example code is available here: https://github.com/kwende/iosMauiBindingLibrary/tree/main. There are two branches: Crashes and Works that show the issue I'm reporting.

  1. Start on an Apple. Open XCode 15.2. Create a new Framework project.
  2. Add a Swift class like this.
  3. Build the XCode project. Use sharpie to generate ApiDefinitions.cs file. The output will look like this.
  4. Go to VS for Mac version 17.6.13 (build 424). Create a new iOS Binding Library (iOS->Library->.NET).
  5. Replace the ApiDefinition.cs with the content shown in step 3.
  6. Add to the Native References the .framework folder from the output of the build from step 3. Build.
  7. On VS for Windows 17.10.1 create a Maui app. Make it specific to iOS. Example here.
  8. Reference the binding library.
  9. Run and get a crash when the app starts on the iPhone. The most notable output is "INFO: dyld[90142]: Symbol not found: (_$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF)".
  10. Now return to step 2 above and change the Swift class to return an int instead of a string. Example here.
  11. Regenerate the ApiDefinitions.cs file with Sharpie again. Continue as before by replacing the ApiDefinitions.cs file in the binding library (example here).
  12. Reference the binding library from the Maui app. Run.
  13. Everything works, no crash.

Expected Behavior

I would expect to be able to return a String from my Swift wrapper classes. It's not clear to me what the issue is. In other tests I've not gotten the linker error, but instead an SIGSEGV when attempting to return a string.

Actual Behavior

I get a crash instead of the string being returned, or the app fully loading. When the app doesn't load properly (which the github example provided shows, you get the following output (note the symbol not found):

INFO: INFO: Starting debugger connection to the app 'iosMauiInterop'. Debugger port: 56486, Device port: 10000 INFO: Launching... INFO: Launching app 'iosMauiInterop' on device... INFO: /Users/ocuvera/Library/Caches/Xamarin/XMA/SDKs/dotnet/packs/Microsoft.iOS.Sdk/17.2.8053/tools/bin/mlaunch --devname "ocv6splus" --killdev "/Users/ocuvera/Library/Caches/Xamarin/mtbs/builds/iosMauiInterop/d0810dea98a7b8ada4fcaf993780d2ca04dd29e133d37482a4735343532f3d35/bin/Debug/net8.0-ios/ios-arm64/device-builds/iphone8.2-15.8.2/iosMauiInterop.app" --launchdev "/Users/ocuvera/Library/Caches/Xamarin/mtbs/builds/iosMauiInterop/d0810dea98a7b8ada4fcaf993780d2ca04dd29e133d37482a4735343532f3d35/bin/Debug/net8.0-ios/ios-arm64/device-builds/iphone8.2-15.8.2/iosMauiInterop.app" -argument=-monodevelop-port -argument=10000 -argument=-connection-mode -argument=usb --setenv=XAMARIN_DEBUG_PORT=10000 --wait-for-unlock -v --sdkroot "/Applications/Xcode.app/Contents/Developer" -sdk 15.8 INFO: [iOS Debugger] Connecting to "ocv6splus" over USB on port 10000... INFO: Creating command connection... INFO: Using Xcode 15.2 found in /Applications/Xcode.app/Contents/Developer INFO: Xamarin.Hosting: Device discovery started INFO: Xamarin.Hosting: Device discovery event: Connected (14134e9c7510e7bb41333d315316ba85971b870a) INFO: Xamarin.Hosting: Connected to ocv6splus (14134e9c7510e7bb41333d315316ba85971b870a) in 00:00:00.0035570 INFO: Xamarin.Hosting: Verbosity: 1 INFO: Xamarin.Hosting: Version: 699ed5eaf2 (refs/heads/main) INFO: Xamarin.Hosting: Xamarin.Hosting INFO: Xamarin.Hosting: Xcode: /Applications/Xcode.app INFO: Xamarin.Hosting: Xcode Version: 15.2 INFO: Xamarin.Hosting: Device discovery event: Connected (14134e9c7510e7bb41333d315316ba85971b870a) INFO: Xamarin.Hosting: Connecting to 'ocv6splus', token is 0x7fc5ee0673d0 INFO: Xamarin.Hosting: Connected to ocv6splus (14134e9c7510e7bb41333d315316ba85971b870a) in 00:00:00.0227203 INFO: Xamarin.Hosting: Mounting developer image on 'ocv6splus' INFO: Xamarin.Hosting: Mounted developer image on 'ocv6splus' INFO: Xamarin.Hosting: Connected to 'ocv6splus' INFO: Launched application 'com.companyname.iosmauiinterop' on 'ocv6splus' with pid 90142 INFO: Xamarin.Hosting: Process '90142' exited with exit code or crashing signal 6. INFO: Application 'com.companyname.iosmauiinterop' terminated (with exit code '' and/or crashing signal '6). INFO: Xamarin.Hosting: Launched com.companyname.iosmauiinterop with PID: 90142 INFO: Referenced from: '/private/var/containers/Bundle/Application/EB50667F-8F0A-44E1-B90A-3F53A2F0D15D/iosMauiInterop.app/Frameworks/iosMauiSwiftLibrary.framework/iosMauiSwiftLibrary' INFO: Expected in: '/System/Library/Frameworks/Foundation.framework/Foundation' INFO: dyld[90142]: Symbol not found: (_$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF) INFO: The application has been launched INFO:

In another app of ours we don't get the missing symbol error, instead we get a crash :

================================================================= Managed Stacktrace:

at <unknown> <0xffffffff>
at CoreFoundation.CFString:CFStringGetLength <0x0003c>
at CoreFoundation.CFString:FromHandle <0x00072>
at Binding.OcuveraAcsSwiftWrapper:EndCall <0x0009a>
at MauiApp1.Platforms.iOS.Invoker:EndCall <0x00030>
at MauiApp1.MainPage:OnCounterClicked <0x0003e>
at Microsoft.Maui.Controls.Button:Microsoft.Maui.Controls.Internals.IButtonElement.PropagateUpClicked <0x0004c>
at Microsoft.Maui.Controls.ButtonElement:ElementClicked <0x000a6>
at Microsoft.Maui.Controls.Button:SendClicked <0x00020>
at Microsoft.Maui.Controls.Button:Microsoft.Maui.IButton.Clicked <0x0001e>
at ButtonEventProxy:OnButtonTouchUpInside <0x0006c>
at UIKit.UIControlEventProxy:Activated <0x0005c>
at System.Object:runtime_invoke_direct_void__this__ <0x00088>

My believe is these are two different results of the same problem: somehow strings aren't able to be returned correctly from Swift functions.

Environment

Version information ``` Visual Studio Professional 2022 for Mac Version 17.6.13 (build 424) Installation UUID: fc7f15f4-1d85-406d-9a6b-e1582c4f2fff Runtime .NET 7.0.3 (64-bit) Architecture: Arm64 Microsoft.macOS.Sdk 13.1.1007; git-rev-head:8afca776a0a96613dfb7200e0917bb57f9ed5583; git-branch:release/7.0.1xx-xcode14.2 Roslyn (Language Service) 4.6.0-3.23180.6+99e956e42697a6dd886d1e12478ea2b27cceacfa NuGet Version: 6.4.0.117 .NET SDK (Arm64) SDK: /usr/local/share/dotnet/sdk/8.0.204/Sdks SDK Versions: 8.0.204 7.0.317 7.0.316 6.0.424 6.0.422 6.0.100 MSBuild SDKs: /Applications/Visual Studio.app/Contents/MonoBundle/MSBuild/Current/bin/Sdks .NET Runtime (Arm64) Runtime: /usr/local/share/dotnet/dotnet Runtime Versions: 8.0.4 7.0.20 7.0.19 6.0.32 6.0.30 6.0.0 Xamarin.Profiler Version: 1.8.0.49 Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler Updater Version: 11 Xamarin.Android Version: 13.2.2.0 (Visual Studio Professional) Commit: xamarin-android/d17-5/45b0e14 Android SDK: /Users/ocuvera/Library/Developer/Xamarin/android-sdk-macosx Supported Android versions: 6.0 (API level 23) 12.0 (API level 31) 11.0 (API level 30) 10.0 (API level 29) 13.0 (API level 33) SDK Command-line Tools Version: 7.0 SDK Platform Tools Version: 33.0.3 SDK Build Tools Version: 32.0.0 Build Information: Mono: d9a6e87 Java.Interop: xamarin/java.interop/d17-5@149d70fe SQLite: xamarin/sqlite/3.40.1@68c69d8 Xamarin.Android Tools: xamarin/xamarin-android-tools/d17-5@ca1552d Microsoft Build of OpenJDK Java SDK: /Library/Java/JavaVirtualMachines/microsoft-11.jdk 11.0.16.1 Android Designer EPL code available here: https://github.com/xamarin/AndroidDesigner.EPL Eclipse Temurin JDK Java SDK: /Library/Java/JavaVirtualMachines/temurin-8.jdk 1.8.0.302 Android Designer EPL code available here: https://github.com/xamarin/AndroidDesigner.EPL Android SDK Manager Version: 17.6.0.50 Hash: a715dca Branch: HEAD Build date: 2024-07-09 00:12:30 UTC Android Device Manager Version: 0.0.0.1309 Hash: 06e3e77 Branch: HEAD Build date: 2024-07-09 00:12:30 UTC Apple Developer Tools Xcode: 15.2 22503 Build: 15C500b Xamarin.Mac Not Installed Xamarin.iOS Version: 16.4.0.23 Visual Studio Professional Hash: 9defd91b3 Branch: xcode14.3 Build date: 2023-10-23 16:15:00-0400 Xamarin Designer Version: 17.6.3.9 Hash: 2648399ae8 Branch: remotes/origin/d17-6 Build date: 2024-07-09 00:12:23 UTC Build Information Release ID: 1706130424 [build-log.txt](https://github.com/user-attachments/files/16289551/build-log.txt) Git revision: acd381f8e8bd7deca64bdf93287dc0849a0e73f5 Build date: 2024-07-09 00:10:15+00 Build branch: release-17.6 Build lane: release-17.6 Operating System Mac OS X 14.5.0 Darwin 23.5.0 Darwin Kernel Version 23.5.0 Wed May 1 20:16:51 PDT 2024 root:xnu-10063.121.3~5/RELEASE_ARM64_T8103 arm64 ```

Build Logs

build-log.txt

Example Project (If Possible)

https://github.com/kwende/iosMauiBindingLibrary

stephen-hawley commented 1 month ago

One thing to keep in mind is that swift strings are...interesting. If you're running on a 64 bit platform, the underlying representation of the string will vary depending on how the string is being used. There is a discriminator which determines the underlying string representation which could also be an inline value in the 64 bits (these are small strings). In the Apple repository you can see what they're doing. It's very complicated. Simply put, Swift String is not NSString and your class is not returning NSString.

What you can try is to rewrite your methods to work in NSString instead of String:

@objc public func returnString(input:NSString)->NSString{
    return ("You gave me " + (input as String)) as NSString
    // or if you want it to be easier to read:
    // return "You gave me \(input)" as NSString
}
kwende commented 1 month ago

@stephen-hawley thank you so much for taking the time to respond to my issue. Unfortunately, I did what you suggested, and I still get a crash at start-up. I have created a 3rd branch now with your suggestion. Example here.

sharpie creates an ApiDefinition.cs class like this (I don't see a change).

The output is this (note the complaint about "symbol not found":

INFO: INFO: Launching... INFO: Creating command connection... INFO: /Users/ocuvera/Library/Caches/Xamarin/XMA/SDKs/dotnet/packs/Microsoft.iOS.Sdk/17.2.8053/tools/bin/mlaunch --devname "ocv6splus" --killdev "/Users/ocuvera/Library/Caches/Xamarin/mtbs/builds/iosMauiInterop/d0810dea98a7b8ada4fcaf993780d2ca04dd29e133d37482a4735343532f3d35/bin/Debug/net8.0-ios/ios-arm64/device-builds/iphone8.2-15.8.2/iosMauiInterop.app" --launchdev "/Users/ocuvera/Library/Caches/Xamarin/mtbs/builds/iosMauiInterop/d0810dea98a7b8ada4fcaf993780d2ca04dd29e133d37482a4735343532f3d35/bin/Debug/net8.0-ios/ios-arm64/device-builds/iphone8.2-15.8.2/iosMauiInterop.app" -argument=-monodevelop-port -argument=10000 -argument=-connection-mode -argument=usb --setenv=XAMARIN_DEBUG_PORT=10000 --wait-for-unlock -v --sdkroot "/Applications/Xcode.app/Contents/Developer" -sdk 15.8 INFO: Starting debugger connection to the app 'iosMauiInterop'. Debugger port: 55894, Device port: 10000 INFO: [iOS Debugger] Connecting to "ocv6splus" over USB on port 10000... INFO: Launching app 'iosMauiInterop' on device... INFO: Using Xcode 15.2 found in /Applications/Xcode.app/Contents/Developer INFO: Xamarin.Hosting: Device discovery started INFO: Xamarin.Hosting: Device discovery event: Connected (14134e9c7510e7bb41333d315316ba85971b870a) INFO: Xamarin.Hosting: Connected to ocv6splus (14134e9c7510e7bb41333d315316ba85971b870a) in 00:00:00.0032911 INFO: Xamarin.Hosting: Xcode: /Applications/Xcode.app INFO: Xamarin.Hosting: Version: 699ed5eaf2 (refs/heads/main) INFO: Xamarin.Hosting: Xamarin.Hosting INFO: Xamarin.Hosting: Xcode Version: 15.2 INFO: Xamarin.Hosting: Verbosity: 1 INFO: Xamarin.Hosting: Device discovery event: Connected (14134e9c7510e7bb41333d315316ba85971b870a) INFO: Xamarin.Hosting: Connected to ocv6splus (14134e9c7510e7bb41333d315316ba85971b870a) in 00:00:00.0204789 INFO: Xamarin.Hosting: Connecting to 'ocv6splus', token is 0x7fc63884ba80 INFO: Xamarin.Hosting: Mounting developer image on 'ocv6splus' INFO: Xamarin.Hosting: Mounted developer image on 'ocv6splus' INFO: Xamarin.Hosting: Connected to 'ocv6splus' INFO: Xamarin.Hosting: Process '25786' exited with exit code 0 or crashing signal . INFO: Launched application 'com.companyname.iosmauiinterop' on 'ocv6splus' with pid 25786 INFO: dyld[25786]: Symbol not found: (_$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF) INFO: Expected in: '/System/Library/Frameworks/Foundation.framework/Foundation' INFO: Referenced from: '/private/var/containers/Bundle/Application/D446834B-12B2-4C55-BF55-F34A9E42FF6B/iosMauiInterop.app/Frameworks/iosMauiSwiftLibrary.framework/iosMauiSwiftLibrary' INFO: Xamarin.Hosting: Launched com.companyname.iosmauiinterop with PID: 25786 INFO: Application 'com.companyname.iosmauiinterop' terminated (with exit code '0' and/or crashing signal ').

stephen-hawley commented 1 month ago

Nice! Thanks for the update. If you're not seeing _bridgeToObjectiveC it means that the app isn't loading the Foundation framework. That's weird to me because in the next line in the log, it tells us that it's looking in /System/Library/Frameworks/Foundation.framework/Foundation which seems reasonable to me. When I look at the disassembly of this code, there is a reference to _bridgeToObjectiveC which points a slightly different path, but I was checking this on macOS. Can you run otool -L on your iosMauiSwiftLibrary to list out which libraries it depends on and verify that that library exists on your device/simulator? This call gets generated by the swift compiler to convert Swift types into the equivalent ObjC types when needed.

kwende commented 1 month ago

@stephen-hawley hello, and thanks again.

So, I'm a native Windows developer, not a Mac/iOS developer. I'm kind of learning on the go here, so in terms of being able to enumerate and identify what files are/aren't on the IOS device (not using an emulator at the moment) I'm not sure how to proceed. The phone isn't jailbreaked and so I'm not sure how to ssh or otherwise connect to the device to find out. That being said, here is the otool output, just in case that helps:

ocuvera@Ocuvera-Mac-Mini iosMauiSwiftLibrary.framework % ls -lah total 192 drwxr-xr-x 7 ocuvera staff 224B Jul 22 14:52 . drwxr-xr-x@ 6 ocuvera staff 192B Jul 22 14:54 .. drwxr-xr-x 4 ocuvera staff 128B Jul 22 14:52 Headers -rw-r--r-- 1 ocuvera staff 768B Jul 22 14:52 Info.plist drwxr-xr-x 4 ocuvera staff 128B Jul 22 14:52 Modules drwxr-xr-x 3 ocuvera staff 96B Jul 22 14:52 _CodeSignature -rwxr-xr-x 1 ocuvera staff 89K Jul 22 14:52 iosMauiSwiftLibrary

ocuvera@Ocuvera-Mac-Mini iosMauiSwiftLibrary.framework % file iosMauiSwiftLibrary iosMauiSwiftLibrary: Mach-O 64-bit dynamically linked shared library arm64

ocuvera@Ocuvera-Mac-Mini iosMauiSwiftLibrary.framework % otool -L iosMauiSwiftLibrary iosMauiSwiftLibrary: @rpath/iosMauiSwiftLibrary.framework/iosMauiSwiftLibrary (compatibility version 1.0.0, current version 1.0.0) /System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 2202.0.0) /usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.62.1) /System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 7209.1.102, weak) /usr/lib/swift/libswiftCore.dylib (compatibility version 1.0.0, current version 5.9.2) /usr/lib/swift/libswiftCoreFoundation.dylib (compatibility version 1.0.0, current version 120.100.0, weak) /usr/lib/swift/libswiftCoreImage.dylib (compatibility version 1.0.0, current version 2.0.0, weak) /usr/lib/swift/libswiftDarwin.dylib (compatibility version 1.0.0, current version 0.0.0, weak) /usr/lib/swift/libswiftDataDetection.dylib (compatibility version 1.0.0, current version 757.6.0, weak) /usr/lib/swift/libswiftDispatch.dylib (compatibility version 1.0.0, current version 34.0.2, weak) /usr/lib/swift/libswiftFileProvider.dylib (compatibility version 1.0.0, current version 1703.62.4, weak) /usr/lib/swift/libswiftMetal.dylib (compatibility version 1.0.0, current version 341.35.0, weak) /usr/lib/swift/libswiftObjectiveC.dylib (compatibility version 1.0.0, current version 8.0.0, weak) /usr/lib/swift/libswiftQuartzCore.dylib (compatibility version 1.0.0, current version 3.0.0, weak) /usr/lib/swift/libswiftos.dylib (compatibility version 1.0.0, current version 1040.0.0, weak) ocuvera@Ocuvera-Mac-Mini iosMauiSwiftLibrary.framework %

Can you advise on a good strategy to identify what files exist on the device so I can get the results back to you?

stephen-hawley commented 1 month ago

OK - you're not going to like this. I built your app and ran it on my local device without any problems - I also reverted back to the original code without the explicit casting. When I tap the button, I get exactly the expected results on an iPhone XS running iOS 17.5.1. I'm sorry that I can't reproduce this for you. I know how frustrating that can be. Not finding the Foundation library is clearly the issue. One thing you can try is to modify your OnCounterClicked method to read like this:

var exists = Directory.Exists("/System/Library/Frameworks/Foundation.framework/") ? "exists" : "doesn't exist";
CounterBtn.Text = exists;

I found on my own system that if I tried to do a File.Exists on /System/Library/Frameworks/Foundation.framework/Foundation I was getting a failure (!!) which makes me suspect that it's living in a cache or that I don't have permissions to touch file or read the directory. Alternately, you could could try doing Directory.GetFiles on /System/Library/Frameworks/Foundation.framework and populate a list with the resulting file names so you can see what (if anything) is in that directory.

kwende commented 1 month ago

@stephen-hawley : that it works for you on your 17.5.1 device was key to me figuring out what's going on (although "figuring out" is probably not the right phrase because I'm still a tad confused).

The problem is that I'm on a really old device (iPhone 6s plus) because our customers are hospitals, and hospitals are notorious for staying on old, "trusted" hardware. That means iOS 15.8.2. Technically our customers' lowest version is 16, but we typically iterate on this hardware "to be sure".

It wasn't until recently we needed to start using binding libraries to interact with Swift code (Azure Communicaiton Services, to be exact), so we've not had to build anything in Xcode until very recently. When building the Swift library, in the Xcode project settings I had set the "Minimum Deployments" to 15.6 (Targets > General > Minimum Deployments), but I had "Deployment Target" set to 17.1 (Project > Info > Deployment Target). When I set the "Deployment Target" to 15.6 the entirety of my project I shared with you works on this old device.

Not being a native Swift/iOS developer with minimal experience in Xcode I mention this in case it indicates a bug I'm not aware of on your guys' side, but it seems likely I just misunderstood what the project settings mean in Xcode and that was the cause of this.

If you agree this is an "us" problem and not a "you" problem, feel free to close this issue out as, for now anyway, we have something working.

stephen-hawley commented 1 month ago

Glad you found the underlying cause!