Description
Background and motivation
There exist certain specialty applications that act as a launcher for other applications within the same process. These applications typically wait for some event to occur (file system change, database change, time elapsed, etc), load a target application, and then execute it.
This works almost perfectly within .NET where the launcher can do something like spawn a thread and quickly get out of the way. However, this remains a tad complicated in the initial bookkeeping of the BCL / runtime that applications rely on. If, as applications sometimes do, they want to invoke Assembly.GetEntryAssembly
, the result they get would obviously be wrong, as it would point to the launcher instead of the application itself. So, this requires changes in order to operate correctly within a launcher.
In order for customers to have that bookkeeping consistency and be able to change the assembly that actually will be run in these scenarios, we would like to propose allowing them to update what the runtime views as the entry assembly, and acts accordingly to that.
API Proposal
namespace System.Reflection;
public class Assembly
{
public static Assembly? GetEntryAssembly(); // Existing API
+ public static void SetEntryAssembly(Assembly assembly);
}
API Usage
public class Program
{
static void Main()
{
var watcher = new FileSystemWatcher
{
Path = "path_to_directory",
Filter = "*.*",
NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += OnChanged;
watcher.EnableRaisingEvents = true;
Console.WriteLine("Press 'q' to quit the sample.");
while (Console.Read() != 'q') ;
}
private static void OnChanged(object source, FileSystemEventArgs e)
{
new Thread(() =>
{
Assembly appToRun = Assembly.LoadFrom("path_to_other_assembly.dll");
MethodInfo entryPoint = appToRun.EntryPoint;
if (entryPoint != null)
{
Assembly.SetEntryAssembly(appToRun);
entryPoint.Invoke(null, new object[] { new string[] { } });
}
}).Start();
}
}
Alternative Designs
One alternative may be to provide an API like the dotnet/arcade RemoteExecutor
that makes it easier to launch a new process with copy of the existing runtime but running a different entry method (potentially from another assembly). That is insufficient for some use cases: some platforms (for example WebAssembly or mobile) do not support launching a new process; in other use cases it may be important to preserve part of the runtime state rather than starting from a brand new process.
Risks
The risk of this new API depends on the depth of the implementation. For diagnostics purposes, there are places in the runtime that cache the string path to the initial entry assembly. We would need to update and potentially change how that information is stored. Additionally, we may want to consider tricks that benefit startup should the entry assembly change early enough (within a startup hook, for example).
Other Notes to Consider
Questions:
- Should this be unsupported on NativeAOT? Trimming?
- Should we allow this to be called multiple times?
- What is the impact on collectable assemblies? For example, if you call
SetEntryAssembly
with an assembly from a collectable ALC and then later set it to something else, should the collectable ALC be able to be collected?