Search code examples
c#.netmauiassembliesmaui-android

"FileNotFoundException: Could not find System.Private.CoreLib.dll" in Release-mode MAUI application


I have built a MAUI application that targets Android. In it, I need to execute code that is given as a string (Yes, I know about the security concerns). I use roslyn's EvaluateAsync for executing the given expression. This worked great during development, but when I switched to the Release-mode build, it suddenly threw an exception as soon as the expression is evaluated. This is the stacktrace of the thrown Exception:

System.IO.FileNotFoundException: Could not find file '/System.Private.CoreLib.dll'.
File name: '/System.Private.CoreLib.dll'
   at Interop.ThrowExceptionForIoErrno(ErrorInfo errorInfo, String path, Boolean isDirError)
   at Interop.CheckIo(Error error, String path, Boolean isDirError)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String path, OpenFlags flags, Int32 mode, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, UnixFileMode openPermissions, Int64& fileLength, UnixFileMode& filePermissions, Boolean failForSymlink, Boolean& wasSymlink, Func`4 createOpenException)
   at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode, Func`4 createOpenException)
   at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.UnixFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean useAsync)
   at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share)
   at Roslyn.Utilities.StandardFileSystem.OpenFile(String filePath, FileMode mode, FileAccess access, FileShare share)
   at Roslyn.Utilities.CommonCompilerFileSystemExtensions.OpenFileWithNormalizedException(ICommonCompilerFileSystem fileSystem, String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare)
   at Microsoft.CodeAnalysis.MetadataReference.CreateFromAssemblyInternal(Assembly assembly, MetadataReferenceProperties properties, DocumentationProvider documentation)
   at Microsoft.CodeAnalysis.MetadataReference.CreateFromAssemblyInternal(Assembly assembly)
   at Microsoft.CodeAnalysis.Scripting.Script.GetReferencesForCompilation(CommonMessageProvider messageProvider, DiagnosticBag diagnostics, MetadataReference languageRuntimeReferenceOpt)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScriptCompiler.CreateSubmission(Script script)
   at Microsoft.CodeAnalysis.Scripting.Script.GetCompilation()
   at Microsoft.CodeAnalysis.Scripting.Script`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].GetExecutor(CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].RunAsync(Object globals, Func`2 catchException, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.Scripting.Script`1[[System.Object, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].RunAsync(Object globals, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.RunAsync[Object](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync[Object](String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScript.EvaluateAsync(String code, ScriptOptions options, Object globals, Type globalsType, CancellationToken cancellationToken)
   at MauiScriptingTest.MainPage.EvaluateScript()

Steps to Reproduce

  1. Create a new .NET MAUI project with .NET 8
  2. Add Reference to Microsoft.CodeAnalysis.CSharp.Scripting, Version 4.8.0
  3. Add the following in MainPage.xaml.cs:
public MainPage()
{
    InitializeComponent();
    this.BindingContext = this;
}

private string? evaluationResult = "Not evaluated yet";
public string? EvaluationResult
{
    get => evaluationResult;
    set
    {
        evaluationResult = value;
        OnPropertyChanged();
    }   
}

private async void OnCounterClicked(object sender, EventArgs e)
{
    await EvaluateScript();
}

private async Task EvaluateScript()
{
    try
    {
        var result = await CSharpScript.EvaluateAsync("return 1+2;");
        EvaluationResult = result.ToString();
    }
    catch (Exception ex)
    {
        EvaluationResult = ex.ToString();
    }

}
  1. Add the following to MainPage.xaml:
<Button
    x:Name="CounterBtn"
    Text="Click me"
    SemanticProperties.Hint="Counts the number of times you click"
    Clicked="OnCounterClicked"
    HorizontalOptions="Center" />

<Label Text="{Binding EvaluationResult}"></Label>
  1. Run the application in Android Emulator (We used Pixel 5, Android 14.0, API 34) in RELEASE MODE. The issue doesn't arise in Debug mode.
  2. Click the button

This should return the result of the calculation "1+2". However, it throws an exception.

I have tried adding references to various assemblies in various different ways that I found online, but nothing worked. I tried disabling trimming and AOT for the project as advised online, but that also didn't work.


Solution

  • According to an answer on the issue I opened:

    File.OpenRead("/System.Private.CoreLib.dll") cannot possibly work on Android, as apps remain compressed on disk after they are installed. The only reason it works in Debug mode, is the way the "fast deployment" feature works: we sideload assemblies and manually copy them to a location. This is not something that would work on app stores/Release mode.

    I don't see how Microsoft.CodeAnalysis.CSharp.Scripting can easily work on Android, unless you have a way to redirect the Stream it opens for files? Perhaps you could try bundling System.Private.CoreLib.dll yourself and extracting it to Environment.SpecialFolder.UserProfile? Not sure if that would work either, though, if it is looking only in / as that is not writeable.

    You might be better off to take a difference approach, in general.

    I ended up using Jint, a Javascript interpreter for .NET.