Assembly Loading Problem in Tizen .NET Applications

Swift Kim

Engineer

You may have encountered the following error when launching a .NET application on your Tizen device, although the DLL file specified by the error message is already owned by the application (in tpkroot).

Note: You can view the log from the Tizen Log Viewer window or dlogutil.

Unhandled exception.
System.IO.FileNotFoundException: Could not load file or assembly 'System.Collections.Immutable, Version=1.2.5.0'.
The system cannot find the file specified.
File name: 'System.Collections.Immutable, Version=1.2.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
   at TizenAppTemplate.App.OnCreate()
   at Tizen.Applications.CoreBackend.UICoreBackend.OnCreateNative(IntPtr data)
   at Tizen.Applications.CoreBackend.UICoreBackend.Run(String[] args)
   at Tizen.Applications.CoreApplication.Run(String[] args)
   at Tizen.Applications.CoreUIApplication.Run(String[] args)
   at TizenAppTemplate.App.Main(String[] args)

Why do I see this error?

The error is a result of a DLL version mismatch. Your application expects a newer version of system DLL than what the device has. This error appears when both of the following conditions are met:

  • The application package contains or has a recursive dependency on one or more system DLLs (System.* or Tizen.*)
  • The application's system DLLs are newer than DLLs installed on the device

This usually happens when you reference a system package from your application. The following example shows an application project file (.csproj) that contains such a reference.

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <TargetFramework>tizen50</TargetFramework>
</PropertyGroup>

<ItemGroup>
  <PackageReference Include="System.Collections.Immutable" Version="1.7.0" />
</ItemGroup>

In some cases, the file doesn't reference the system package directly. However, it may have a recursive dependency in its dependency graph.

<ItemGroup>
  <!-- This library is dependent on System.Collections.Immutable 1.7.0. -->
  <PackageReference Include="SomeLibrary" Version="1.0.0" />
</ItemGroup>

Note: AssemblyVersion, AssemblyFileVersion, and NuGet PackageVersion are different version properties. For example, the NuGet package System.Collections.Immutable version 1.7.0 (NuGetPackageVersion) comes with System.Collections.Immutable.dll version 1.2.0.5 (AssemblyVersion). Only AssemblyVersion is used by the runtime to verify DLL dependencies.

The following example shows the AssemblyVersion of the DLL used by the application and the AssemblyVersion of the DLL installed on the system.

### Application-owned DLL
$ sdb pull /opt/usr/globalapps/org.tizen.example.TizenAppTemplate/bin/System.Collections.Immutable.dll
$ dotnet-ildasm System.Collections.Immutable.dll | grep '\.ver' | tail -n 1
.ver 1:2:0:5

### Pre-installed DLL
$ sdb pull /usr/share/dotnet.tizen/netcoreapp/System.Collections.Immutable.dll
$ dotnet-ildasm System.Collections.Immutable.dll | grep '\.ver' | tail -n 1
.ver 1:2:0:4

The current implementation of the application launcher in Tizen (which hosts the .NET runtime) always prioritizes DLLs found on the device and disregards application-owned DLLs, even though they are newer. It also ignores any properties defined in the application's .deps.json file.

In fact, this restriction is intentionally added in Tizen for performance reasons. The launcher pre-fetches several runtime components commonly used by applications (for reduced startup time) by assuming a unified set of Trusted Platform Assemblies across all applications. Because of the fundamental design of CoreCLR, a DLL cannot be loaded into the default LoadContext if a DLL with the same name exists in the Trusted Platform Assemblies.

For further details, you can read:

Possible solutions

Here are some solutions you can try:

  • Remove or downgrade the package

    The first, most basic workaround you can try is to eliminate the dependency itself. Check if the package is being used in the code and, if possible, re-implement the code without the package. Otherwise, downgrade the package from NuGets so that its DLL versions are compatible with all target devices. However, this may not be feasible if you cannot find a working version or you cannot control the dependencies of the libraries used by your application.

    <ItemGroup>
      <!--<PackageReference Include="System.Collections.Immutable" Version="1.7.0" />-->
      <PackageReference Include="System.Collections.Immutable" Version="1.6.0" />
    </ItemGroup>
    
  • Handle AssemblyResolve events

    Another workaround is to install a custom AssemblyResolve event handler inside your application. This handler is invoked when the runtime cannot find a DLL with a matching version. Note that if an application DLL has a native image, the native image is not automatically resolved by Assembly.LoadFile().

    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += (object s, ResolveEventArgs eventArgs) =>
        {
            var appDir = Path.GetDirectoryName(typeof(App).Assembly.Location);
            var assemblyName = eventArgs.Name.Split(',')[0];
            var assemblyPath = Path.Combine(appDir, assemblyName + ".dll");
            return File.Exists(assemblyPath) ? Assembly.LoadFile(assemblyPath) : null;
        };
        ..
        app.Run(args);
    }
    

    Use this API with care since it can cause some unexpected behaviors, such as a type identity problem (see Best Practices for Assembly Loading). Also, you cannot use Assembly.Load() (with a full assemblyRef as an argument) or Assembly.LoadFrom() because they implicitly load an assembly into the default LoadContext and this leads to a similar error:

    Unhandled exception.
    System.IO.FileLoadException: Could not load file or assembly 'System.Collections.Immutable, Version=1.2.5.0'.
    Could not find or load a specific file. (0x80131621)
    File name: 'System.Collections.Immutable, Version=1.2.5.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
     ---> System.IO.FileLoadException: Could not load file or assembly 'System.Collections.Immutable, Version=1.2.5.0'.
       at System.Runtime.Loader.AssemblyLoadContext.InternalLoadFromPath(String assemblyPath, String nativeImagePath)
       at System.Runtime.Loader.AssemblyLoadContext.LoadFromAssemblyPath(String assemblyPath)
       at System.Reflection.Assembly.LoadFrom(String assemblyFile)
       ..
    
  • Use custom AssemblyLoadContext

    For advanced scenarios, you can create your own AssemblyLoadContext in your application to isolate any specific DLLs. The following CustomAssemblyLoadContext class overrides the Load() method to resolve DLLs from the current application directory. See Understanding System.Runtime.Loader.AssemblyLoadContext for detailed information.

    private class CustomAssemblyLoadContext : AssemblyLoadContext
    {
        protected override Assembly Load(AssemblyName assemblyName)
        {
            var appDir = Path.GetDirectoryName(typeof(App).Assembly.Location);
            var assemblyPath = Path.Combine(appDir, assemblyName.Name + ".dll");
            return File.Exists(assemblyPath) ? LoadFromAssemblyPath(assemblyPath) : null;
        }
    }
    
    // In application code,
    var alc = new CustomAssemblyLoadContext();
    var assembly = alc.LoadFromAssemblyName(new AssemblyName("SomeLibrary"));
    assembly.GetType("SomeLibrary.SomeClass").GetMethod("SomeMethod").Invoke(null, null);
    

The sample code shown above may not work if the prefer_dotnet_aot manifest property is enabled in tizen-manifest.xml because application DLLs can have their native images located in either bin or bin/.native_image. In such cases, consider using ApplicationInfo.ExecutablePath from the TizenFX API instead of Assembly.Location.

If you need help, please contact me at swift.kim@samsung.com.

For further information about Tizen .NET, see Samsung Developers Tizen.