Skip to content

[API Proposal]: Assembly.SetEntryAssembly() #101616

Closed

Description

@ivdiazsa

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?

Activity

  1. added
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Apr 26, 2024
  2. ghost added
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Apr 26, 2024
  3. added this to the 9.0.0 milestone on Apr 26, 2024
  4. added and removed
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Apr 26, 2024
  5. dotnet-policy-service commented on Apr 26, 2024

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-reflection
    See info in area-owners.md if you want to be subscribed.

  6. jkotas commented on Apr 26, 2024

    @jkotas
    Member

    Should this be unsupported on NativeAOT? Trimming?

    There is nothing NativeAOT or trim incompatible in this API.

    Should we allow this to be called multiple times?

    Yes.

    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?

    Yes.

  7. jkotas commented on Apr 26, 2024

    @jkotas
    Member

    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.

    This is best effort. We won't be ever able to fully abstract away the fact that the application was launched through a launcher. For example, https://learn.microsoft.com/dotnet/api/system.diagnostics.process.mainmodule is going to point to the launcher .exe and not to the app.exe, and it is not something that we want to shim. If there are scenarios where diagnostic tools need to know about the current entrypoint, we can always add new diagnostic APIs to handle the new scenario if the existing diagnostic APIs are not sufficient.

  8. jkotas commented on Apr 26, 2024

    @jkotas
    Member

    The proposal LGTM! Feel free to flip it to api-ready-to-review. (Also, mark it as blocking if you would like it to be reviewed with higher priority.)

  9. added
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    blockingMarks issues that we want to fast track in order to unblock other important work
    and removed
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Apr 26, 2024
  10. stephentoub commented on Apr 29, 2024

    @stephentoub
    Member

    public void SetEntryAssembly(Assembly assembly);

    Is this supposed to be static?

  11. MichalStrehovsky commented on May 6, 2024

    @MichalStrehovsky
    Member

    Will there be any validation on the assembly used? E.g.:

    • Does it have to be the corelib-defined RuntimeAssembly, or can it be an assembly from e.g. MetadataLoadContext?
    • Can it be a reflection-emitted assembly, or an assembly loaded from a byte array (it's very common for apps to do Assembly.GetEntryAssembly().Location and dynamically loaded assemblies will have an empty Location).
    • Does it need a non-null EntryPoint?
  12. jkotas commented on May 6, 2024

    @jkotas
    Member

    We may want to check that it is corelib-defined RuntimeAssembly as a sanity check. I do not think we need any other extraneous validation. It is up to the caller of this API to ensure that it does not break the rest of the app.

  13. bartonjs commented on May 14, 2024

    @bartonjs
    Member

    Video

    Looks good as proposed

    namespace System.Reflection;
    
    public partial class Assembly
    {
        public static void SetEntryAssembly(Assembly assembly);
    }
  14. added
    api-approvedAPI was approved in API review, it can be implemented
    and removed
    blockingMarks issues that we want to fast track in order to unblock other important work
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    on May 14, 2024
  15. ivdiazsa commented on May 14, 2024

    @ivdiazsa
    ContributorAuthor

    Thank you @bartonjs!

  16. WeihanLi commented on May 15, 2024

    @WeihanLi
    Contributor

    Trying to understand the use case, would this update the Assembly.GetEntryAssembly() value, seemed there are a lot of code using Assembly.GetEntryAssembly, if updated, not sure if there would be some side effects

    https://grep.app/search?q=Assembly.GetEntryAssembly%28%29&filter[lang][0]=C%23

  17. jkotas commented on May 29, 2024

    @jkotas
    Member

    Fixed by #102271

  18. locked and limited conversation to collaborators on Jun 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions