镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Expose managed Main with NativeAOT when compiling the application as a static library #81097

Closed

Description

@ivanpovazan

Description

In order to integrate Xamarin and NativeAOT when targeting iOS platforms the following scenario is used:

  • Xamarin acts as a hosting native application which is statically linked with NativeAOT runtime, AOTed managed application and all the required libraries.
  • Xamarin's startup sequence initialises NativeAOT runtime, the managed ObjCRuntime and invokes the managed Main method to start the application.

However, if the application is compiled as a static library with the ILCompiler, the managed Main method is not exposed to the native world.

To workaround this limitation, one could change the source code of the application and add a method with UnmanagedCallersOnlyAttribute that wraps the Main, but if we consider the experience of the end-user, there should be an automatic way of injecting the required code which would make the transition to NativeAOT seamless.

Activity

  1. added this to the 8.0.0 milestone on Jan 24, 2023
  2. am11 commented on Jan 24, 2023

    @am11
    Member

    if the application is compiled as a static library with the ILCompiler

    I haven't tested it, but I think it should possible today. Which symbols to keep in static library scenario is up to the final linkage which in our case is left to the end-user (and we don't provide supporting script / build steps today: #70277). With -p:StaticExecutable=true, we have control over which symbols to keep in the final linkage of executable, which can be modified.

  3. ivanpovazan commented on Jan 25, 2023

    @ivanpovazan
    MemberAuthor

    @am11 I was more referring to how ILCompiler compiles a native library vs an executable and the symbols it exposes via corresponding *RootProviders:

    That being said, an executable compiled as a native library will not have its managed Main method exposed (and no way to pass parameters to it).

  4. jkotas commented on Jan 27, 2023

    @jkotas
    Member

    There are number of things that happen around Main. For example, the arguments passed to Main are stored so that they can be also retrieved via Environment.GetCommandLineArgs() API, the runtime waits for other foreground to finish before proceeding to exit, the latched exit code is used as the exit code if the Main method does not return it, etc.

    I assume that you want some or all of these to be included in the Main method wrapper. Is that right?

    We should agree on the details like this before you proceed with implementation, so that it does not have to be reworked.

  5. AustinWise commented on Jan 29, 2023

    @AustinWise
    Contributor

    In case it's helpful, the approach I took for my proof-of-concept was to generate both the library and main method entry points in ILC. I then used the library version of bootstrapper to enable UnamangedCallersOnly functions to be called before the managed main method. This enables you to provide your own native main function, but otherwise compile and run like a normal nativeaot executable:

    AustinWise@810f978

    The managed main in this proof of concept still contains all those features Jan mentions, so I'm not sure its what you want.

  6. ivanpovazan commented on Jan 30, 2023

    @ivanpovazan
    MemberAuthor

    @jkotas from the mentioned set of features around managed Main, what does ILC support by exposing __managed__Main?


    @AustinWise thank you, that looks like something we would want.
    I was wondering would it make sense to always expose __managed__Main when: -p:OutputType=Exe -p:NativeLib=Static

  7. jkotas commented on Jan 30, 2023

    @jkotas
    Member

    @jkotas from the mentioned set of features around managed Main, what does ILC support by exposing __managed__Main?

    All of them and more.

    The IL code for __managed__Main is generated here:

    .

    I do not think you want the full existing __managed__Main for your purpose. For example, I do not think you want to run the library initializers. They are run by the library initialization. Running them twice is not a good idea.

  8. MichalStrehovsky commented on Feb 8, 2023

    @MichalStrehovsky
    Member

    I agree with what Jan wrote, we don't want to run both the library startup code and EXE startup code at the same time - they would run the same things twice. However, we could go with something similar to what @AustinWise suggested in #81097 (comment). (We just need to address the double execution.)

    On a high level:

    The NativeLibraryStartupMethod (the library case) will:

    • Initialize CoreLib and friends (internal implementation detail not to worry about - just need to make sure nothing else runs before these - managed code cannot even do a new before these run)
    • Run module initializers (this is potentially user code)

    On the other hand, StartupCodeMainMethod (the exe case) will:

    • Initialize CoreLib and friends (same as above)
    • Store command line args so that Environment.GetCommandLineArgs can access them
    • Store entrypoint assembly so that Assembly.GetEntryAssembly works.
    • Initialize COM apartment state (not relevant outside Windows)
    • Run module initializers (same as above)
    • Call user code Main
    • Store return value of Main in Environment class (if it's non-void)
    • Shut down CoreLib (wait for foreground threads, call user code in AppContext.OnProcessExit, etc.)
    • Exit with the exit code latched in the Environment class

    One simple solution based on @AustinWise commit would be to add a bool flag to StartupCodeMainMethod class indicating whether to generate CoreLib initialization and module constructors. Then the workflow would be:

    • NativeLibraryStartupMethod-generated code does its thing as the first thing when the module is loaded, as part of static initialization. NOTE: it will run user code in module constructors, and these module constructors won't see correct Environment.GetCommandLineArgs and Assembly.GetEntryAssembly if they ask.
    • At some point the outside host calls __managed_main and passes command line arguments. StartupCodeMainMethod-generated code will run. This generated code will do everything as usual, but the corelib-initialization and module constructor running would be skipped.

    If this is not desirable, we'll need to come up with some other ordering. I think it mainly depends on when do we want to run module initializers.

  9. ivanpovazan commented on Feb 8, 2023

    @ivanpovazan
    MemberAuthor

    Thank you for the detailed explanation.

    One simple solution based on @AustinWise commit would be to add a bool flag to StartupCodeMainMethod class indicating whether to generate CoreLib initialization and module constructors.

    This approach sounds good to me. @rolfbjarne what do you think?

  10. rolfbjarne commented on Feb 8, 2023

    @rolfbjarne
    Member

    Thank you for the detailed explanation.

    One simple solution based on @AustinWise commit would be to add a bool flag to StartupCodeMainMethod class indicating whether to generate CoreLib initialization and module constructors.

    This approach sounds good to me. @rolfbjarne what do you think?

    Yes, I think that sounds good too.

  11. ivanpovazan commented on Feb 8, 2023

    @ivanpovazan
    MemberAuthor

    @AustinWise, would you be interested in taking over this issue?

  12. AustinWise commented on Feb 8, 2023

    @AustinWise
    Contributor

    Sure, I can create a PR.

  13. ivanpovazan commented on Feb 8, 2023

    @ivanpovazan
    MemberAuthor

    thanks a lot! I have assigned the issue to you, and let me know if you need any help, I would gladly assist if needed.

  14. AustinWise commented on Feb 9, 2023

    @AustinWise
    Contributor

    @ivanpovazan

    I created #81873 with my changes. I was not sure how this should fit into the larger build system, so I made the ILC parts flexible.

  15. ivanpovazan commented on Feb 15, 2023

    @ivanpovazan
    MemberAuthor

    Closed via: #81873
    Thank you @AustinWise!

  16. ghost locked as resolved and limited conversation to collaborators on Mar 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions