Assembly: Esri.ArcGISRuntime (in Esri.ArcGISRuntime.dll) Version: 100.11.0
Has anyone noticed that the documentation lists "InstallPath" as a property, but the assembly decomposition does not show it?
(Assembly Esri.ArcGISRuntime, Version=100.11.0.0, Culture=neutral, PublicKeyToken=8fc3cc631e44ad86)
Is the runtime still capable of shared deployments?
We run our deployment pointing to a shared location for a couple of different applications.
I'm assuming you're targeting .NET Core/.NET 5 and not .NET Framework? Unfortunately our documentation doesn't show doc for both (we're actively working on that - stay tuned!), but the property is only available with .NET Framework, since there's now a newer better way to load native libraries dynamically than .NET Framework provided.
If you can share your scenario for setting this property, I can help provide you with some equivalent code in .NET Core / NET 5.
Yes, this is part of a transition for a .NET Framework to .NET 5
Our previous usage was fairly simple, we created an installer for the shared items and then in the consuming application before we begin using the runtime we set the InstallPath to where the shared installation is.
I am definitely interested this topic, and one other, related to .NET 5 migration, if anyone can point me in the right direction. Microsoft doc searches keep pointing me back to the old Framework.
My gratitude always,
--Mark
Update: I think I finally found what I'm looking for:
https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/default-probing
Replacing Assembly.LoadFrom with AssemblyLoadContext.Default.LoadFromAssemblyPath cleared up the resource DLL problem. Now I'll look into the possibility of using the native dll search options to point to a shared Runtime deployment.
Nice to know. The effort I originally discovered this under was for a migration to another project. We had the ESRI Runtime access contained in a library so it could be swapped out if needed. This had it deployed as a shared resource. The new project didn't see the need to use a shared library so it was integrated into the project which then removed the need to specify the "InstallPath" hence why I did not pursue the issue any farther.
Here's the gist of how to hint .NET Core where to look for native libraries to load:
private static IntPtr runtimeLibPtr = IntPtr.Zero;
private static IntPtr runtimeLibWpfPtr = IntPtr.Zero;
private const string version = "100_13";
private const string InstallPath = @"C:\ArcGISRuntimeInstall\";
private static IntPtr DllImportResolver(string libraryName, System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportSearchPath? searchPath)
{
string path = $"{InstallPath}x{(Environment.Is64BitProcess ? "64" : "86")}\\{libraryName}";
if (libraryName == "RuntimeCoreNet" + version + ".dll" && assembly.FullName == typeof(Esri.ArcGISRuntime.ArcGISRuntimeEnvironment).Assembly.FullName)
{
if (runtimeLibPtr == IntPtr.Zero)
{
if (System.Runtime.InteropServices.NativeLibrary.TryLoad(@$"{path}", out runtimeLibPtr))
return runtimeLibPtr;
}
return runtimeLibPtr;
}
else if (libraryName == "RuntimeCoreNet" + version + ".WPF.dll" && assembly.FullName == typeof(Esri.ArcGISRuntime.UI.Controls.MapView).Assembly.FullName)
{
if (runtimeLibWpfPtr == IntPtr.Zero)
{
if (System.Runtime.InteropServices.NativeLibrary.TryLoad(@$"{path}", out runtimeLibPtr))
return runtimeLibWpfPtr;
}
return runtimeLibWpfPtr;
}
return IntPtr.Zero;
}
You would then hook this Dll import resolver up at app startup before calling into any of our APIs:
System.Runtime.InteropServices.NativeLibrary.SetDllImportResolver(typeof(Esri.ArcGISRuntime.ArcGISRuntimeEnvironment).Assembly, new System.Runtime.InteropServices.DllImportResolver(DllImportResolver));
System.Runtime.InteropServices.NativeLibrary.SetDllImportResolver(typeof(Esri.ArcGISRuntime.UI.Controls.MapView).Assembly, new System.Runtime.InteropServices.DllImportResolver(DllImportResolver));
I've run into two issues:
Unable to find an entry point named 'CoreRT_GeoView_setScreenScale' in DLL 'RuntimeCoreNet100_12.dll'.
To clarify, I set up what I thought was a simple test, but if I made a glaring error another set of eyes always helps. I created a deployment which looks like this:
And here's the code for App.xaml.cs:
using Esri.ArcGISRuntime;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Threading.Tasks;
using System.Windows;
namespace DeploymentTest
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private static IntPtr runtimeLibPtr = IntPtr.Zero;
private static IntPtr runtimeLibWpfPtr = IntPtr.Zero;
private const string DeploymentHome = @"C:\apps";
private const string RuntimeVersion = "100.12";
private static string NativeInstallPath;
private static string DLLversion;
private void Application_Startup(object sender, StartupEventArgs e)
{
try
{
string sRuntimeDeployment = "ArcGISRuntimeCore" + RuntimeVersion;
string sInstallPath = Path.Combine(DeploymentHome, sRuntimeDeployment);
if (Directory.Exists(sInstallPath))
{
NativeInstallPath = Path.Combine(sInstallPath, "runtimes");
DLLversion = RuntimeVersion.Replace('.', '_');
NativeLibrary.SetDllImportResolver(typeof(ArcGISRuntimeEnvironment).Assembly, new DllImportResolver(DllImportResolver));
NativeLibrary.SetDllImportResolver(typeof(Esri.ArcGISRuntime.UI.Controls.MapView).Assembly, new DllImportResolver(DllImportResolver));
string sAssemblyPath = Path.Combine(sInstallPath, "Esri.ArcGISRuntime.dll");
AssemblyLoadContext.Default.LoadFromAssemblyPath(sAssemblyPath);
sAssemblyPath = Path.Combine(sInstallPath, "Esri.ArcGISRuntime.WPF.dll");
AssemblyLoadContext.Default.LoadFromAssemblyPath(sAssemblyPath);
}
// Initialize the ArcGIS Runtime before any components are created.
ArcGISRuntimeEnvironment.Initialize();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "ArcGIS Runtime initialization failed.");
// Exit application
this.Shutdown();
}
}
private static IntPtr DllImportResolver(string libraryName, System.Reflection.Assembly assembly, System.Runtime.InteropServices.DllImportSearchPath? searchPath)
{
string path = $"{NativeInstallPath}\\win-x{(Environment.Is64BitProcess ? "64" : "86")}\\native\\{libraryName}";
if (libraryName == "RuntimeCoreNet" + DLLversion + ".dll") // && assembly.FullName == typeof(Esri.ArcGISRuntime.ArcGISRuntimeEnvironment).Assembly.FullName)
{
if (runtimeLibPtr == IntPtr.Zero)
{
if (NativeLibrary.TryLoad(@$"{path}", out runtimeLibPtr))
return runtimeLibPtr;
}
return runtimeLibPtr;
}
else if (libraryName == "RuntimeCoreNet" + DLLversion + ".WPF.dll") // && assembly.FullName == typeof(Esri.ArcGISRuntime.UI.Controls.MapView).Assembly.FullName)
{
if (runtimeLibWpfPtr == IntPtr.Zero)
{
if (NativeLibrary.TryLoad(@$"{path}", out runtimeLibPtr))
return runtimeLibWpfPtr;
}
return runtimeLibWpfPtr;
}
return IntPtr.Zero;
}
}
}
I don't do a lot of dynamic DLL loading, but the times I have done so, have shown me that any reference to the type before the load is complete is catastrophic.
As such, would not lines 39, 40 cause problems as they attempt to get a Type reference from a type before the library load is performed/finished?