Esri.ArcGISRuntime.ArcGISRuntimEnvironment.InstallPath

1293
14
07-12-2021 10:26 AM
TroyAvery1
New Contributor II

Assembly: Esri.ArcGISRuntime (in Esri.ArcGISRuntime.dll) Version: 100.11.0

https://developers.arcgis.com/net/wpf/api-reference/html/P_Esri_ArcGISRuntime_ArcGISRuntimeEnvironme...

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.

0 Kudos
14 Replies
dotMorten_esri
Esri Frequent Contributor

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.

TroyAvery1
New Contributor II

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.

0 Kudos
MarkCederholm
Regular Contributor II

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. 

  1.  Is there a simple way, not hacking into JSON configs, to point Runtime to a particular shared deployment path?
  2.  Is there a way to tell assembly probing NOT to look for .resource dlls when using Assembly.LoadFrom and Assembly.CreateInstance?

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

0 Kudos
MarkCederholm
Regular Contributor II

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.

0 Kudos
TroyAvery1
New Contributor II

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.

0 Kudos
dotMorten_esri
Esri Frequent Contributor

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));

 

0 Kudos
MarkCederholm
Regular Contributor II

I've run into two issues:

  1. Shared resources are still expected to reside in the application folder
  2. I'm getting the following error: 

    Unable to find an entry point named 'CoreRT_GeoView_setScreenScale' in DLL 'RuntimeCoreNet100_12.dll'.

0 Kudos
MarkCederholm
Regular Contributor II

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:

Deployment.png

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;
		}
	}
}
0 Kudos
TroyAvery1
New Contributor II

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?

0 Kudos