-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[NativeAOT] support calling UnmanagedCallersOnly functions before managed main() #77957
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
This sounds like an AOT variant of startup hook (https://github.com/dotnet/runtime/blob/main/docs/design/features/host-startup-hook.md). |
Basically, yes. Something like a start up hook won't let you manipulate the command line arguments before the managed main, but that's not required for the xamarin-macios use case I had in mind. |
If we don't need the command line manipulation, can we do this using the module constructor approach? The only problem with that is that C# doesn't allow ordering the module constructor fragments, but if we do it from MSBuild we can have this to be the first file and that probably means it will be the first method to be called from the module constructor too. |
I was wondering whether it would be possible to have:
Wouldn't this work? |
Yes, it is possible to achieve this by custom build steps that modify the app, e.g. by injecting a module constructor or wrapping the Main method. Again, it looks similar to startup hook. It was always possible to approximate what startup hook does by writing a custom host or by modifying the app. We had multiple customers building complex fragile solutions to solve the problem. We have decided to introduce the startup hook feature to simplify the life of these customers (and ours too since we would often end up debugging issues in the complex fragile solutions). So, the question is whether we believe that injecting code before Main is common enough scenario for native AOT to introduce a feature for it. |
What would the requirements look like? Startup hook allows introducing code before A Native AOT equivalent would be to dlopen/dlsym (LoadLibrary/GetProcAddress) whatever was in the environment variable and p/invoke into it. Or we could say we only want a compile-time startup hook. This is a lot less powerful than startup hooks. I have a hard time seeing difference from just adding a module initializer. Neither of those would allow modifying the command line arguments though which looks like a requirement for Xamarin (and definitely a one-off that is hard to imagine a general purpose solution for). |
Module initializer requires appending a new .cs file to the app sources. It is certainly doable. I am not sure how hard it is to hide it so that it does not show as part of the solution in the GUI. |
The default targets already generate two such files into |
I think the idea of injecting a module constructor into the main program from the SDK would work fine. |
Reopening this issue as it became a requirement of a broader work item. @AustinWise thanks for your suggestions and contributions! Would you be interested in contributing in resolving this issue? |
@ivanpovazan If we go with injecting a module constructor into the main program from the SDK, should this issue be tracked in xamarin-macios repo instead where the relevant SDK lives? |
@jkotas if I am not mistaken, I believe the requirement is more general. Example: someone has a desktop managed application compiled as a static library with NativeAOT, and wants to integrated it into a native application. For this reason, I thought the change belongs to: https://github.com/dotnet/runtime/blob/main/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets What do you think? |
This should not be Native-AOT specific change. It should be equally applicable to all runtimes. |
Note that the issue title does not match the design that we have converged to: #77957 (comment) |
@jkotas, thank you. |
Note also that we recently enabled startup hooks for mobile platforms (running on Mono #80391). The It seems like which approach we use depends on how we want to think about the ios app. If we compile it as a static library, then the startup sequence is: Microsoft.iOS starts in native -> calls some UnmanagedCallersOnly methods (a Main wrapper) in Microsoft.iOS and then -> calls the user's Main. If we make an executable, we could instead do: NativeAOT native main starts -> executes startup hooks which start Microsoft.iOS; Native AOT native main -> calls managed Main which runs in an environment configured by the startup hook. |
After internal discussions, it has been concluded that there will be no need for injecting module constructor into the main program, as Xamarin will implement a new managed-to-native bridge for NativeAOT. The startup code will be responsible of invoking any required bootstrap and/or initialization managed code in the correct order, by utilizing symbols exposed via A pseudocode example of a native startup sequence would be:
where The progress of the work on Xamarin side is tracked here: xamarin/xamarin-macios#17339 For this reason I am closing the issue to avoid any further confusion. |
We do not plan to support startup hooks for native AOT compiled apps. Startup hooks are meant to be used for dynamic non-trimmed scenarios where a new component is injected into the app, e.g. to light-up a new diagnostic capability that the app is not aware of. |
I think providing capabilities of which the app is not aware and being dynamic and non-trimmable are separate issues. Startup hooks could be used in static, trimmed, scenarios on mobile. Providing a diagnostic capability of which the app is not aware can be done at publish time for debug configurations, for example. The startup hook is in the |
Things get a bit more complicated when it comes to iOS App Extensions. There are very much like normal app bundles (they have a native executable for instance), but the executable project doesn't have a native Our way of dealing with this is to provide a native To summarize I think this scenario can be simplified as "we don't always call managed Main, but another native function instead". It seems this is harder/more work to implement on the NativeAOT side if NativeAOT creates an executable instead of a static library. I'm also favoring the first approach (static library) because that matches how we're doing things with both Mono and CoreCLR, and having a similar design for all options makes the code easier to understand and maintain on our side. Also worthy of note is that iOS App Extensions might be one of the more interesting targets for NativeAOT, because they can be severely constrained compared to normal iOS apps (max disk space requirements, max memory requirements, etc.) |
) This PR adds a new flag to ILC, `--splitinit` . The flag splits the initialization that is normally done for executable into two parts. The `__managed__Startup` function runs classlib and module initializers. The `__managed__Main` function performs the remaining initialization and executes the entry-point of the managed application. The use case for this is to allow calling `UnamanagedCallersOnly` functions before the managed `main` function. Running on iOS is the first use case for this feature (#80905). It was not clear to me how to fit this into the larger NativeAot build system. Should this be thought of as "a static library that also has a `__managed__Main` compiled in" or as "an executable that splits its initialization into two parts"? So I added support to ILC for both cases: compiling with the `--nativelib` flag and without it. Lastly, I added some build integration the "an executable that splits its initialization into two parts" case, along with test. The idea is the user sets the `CustomNativeMain` property in their project. They are then responsible for providing a `NativeLibrary` item pointing to an object file containing their native `main` function. At runtime, when they call their first managed function, NativeAOT initialization will run. This includes calling `__managed__Main`, so the user cannot forget to initialize the runtime. Related issues: #81097 #77957
Problem statement
Some existing applications use the CoreCLR hosting APIs to call managed functions before calling the main method of an assembly. A specific example is xamarin-macios. It uses
coreclr_create_delegate
and reverse P/Invoke to call some functions before usingcoreclr_execute_assembly
to execute an assembly's main function.While trying to replicate the CoreCLR hosting API does not make sense for NativeAOT, the ability to call managed functions before main() would be useful.
Implementation Ideas
Currently ILC will generate one of two entry points:
__managed__Main
for executables and__managed__Startup
for libraries. If both were generated, a hosting application could call functions in the following order:__managed__Startup
UnmanagedCallersOnly
functionsargc
andargv
if desired.__managed__Main
to run the application.When generating both entry points,
__managed__Main
should not run library or module initializers.Ass a proof of concept, see this example adding support to ILC in this commit and then using those entry points with a custom bootstrapper to emulate enough of the CoreCLR hosting API to make xamarin-macios work.
Alternatives Considered
Conceivably hosts like xamarin-macios could use module constructors to run code before the main method, as NativeAOT eagerly runs these at startup. The only missing feature when using this approach is the ability to manipulate command line arguments before the main method starts.
Open Questions
Related issues
The text was updated successfully, but these errors were encountered: