| File: ManagedHost.cs | Web Access |
| Project: src\sdk\src\Cli\dotnet-aot\dotnet-aot.csproj (dotnet-aot) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. extern alias NativeWrapper; using System.Diagnostics; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.NativeWrapper; using NativeConstants = NativeWrapper::Microsoft.DotNet.NativeWrapper.Constants; namespace Microsoft.DotNet.Cli; /// <summary> /// Hosts the .NET runtime and provides the ability to load and invoke managed assemblies. /// Designed to be initialized asynchronously so the runtime is ready when needed. /// </summary> internal sealed unsafe class ManagedHost : IDisposable { private nint _hostContextHandle; private delegate* unmanaged[Cdecl]<nint, nint, nint, nint, nint, nint, int> _loadAssemblyAndGetFunctionPointer; private Task? _initTask; private readonly string _runtimeConfigPath; private readonly string _dotnetRoot; private bool _disposed; /// <summary> /// Creates a new <see cref="ManagedHost"/> for the given runtime config. /// </summary> /// <param name="runtimeConfigPath">Path to the .runtimeconfig.json file.</param> /// <param name="dotnetRoot">Path to the .NET installation root.</param> public ManagedHost(string runtimeConfigPath, string dotnetRoot) { _runtimeConfigPath = runtimeConfigPath; _dotnetRoot = dotnetRoot; } /// <summary> /// Starts initializing the .NET runtime asynchronously. /// </summary> public void StartInitialization() { _initTask = Task.Run(Initialize); } /// <summary> /// Ensures the runtime is initialized, blocking if necessary. /// </summary> public void EnsureInitialized() { if (_initTask is null) { Initialize(); } else { _initTask.GetAwaiter().GetResult(); } } /// <summary> /// Loads the specified assembly and invokes an [UnmanagedCallersOnly] method /// with the default component entry point signature: int fn(nint args, int sizeBytes). /// </summary> /// <param name="assemblyPath">Full path to the managed assembly.</param> /// <param name="typeName">Fully qualified type name (e.g., "Microsoft.DotNet.Cli.Program, dotnet").</param> /// <param name="methodName">Method name to invoke.</param> /// <returns>The exit code returned by the managed method.</returns> public int InvokeMethod(string assemblyPath, string typeName, string methodName) { EnsureInitialized(); if (_loadAssemblyAndGetFunctionPointer is null) { throw new InvalidOperationException("Runtime initialization failed - load_assembly_and_get_function_pointer is not available."); } nint functionPointer; nint assemblyPathNative = PlatformStringMarshaller.ConvertToUnmanaged(assemblyPath); nint typeNameNative = PlatformStringMarshaller.ConvertToUnmanaged(typeName); nint methodNameNative = PlatformStringMarshaller.ConvertToUnmanaged(methodName); try { // delegate_type_name = -1 means UNMANAGEDCALLERSONLY_METHOD, reserved = 0 int result = _loadAssemblyAndGetFunctionPointer( assemblyPathNative, typeNameNative, methodNameNative, -1, 0, (nint)(&functionPointer)); if (result != 0) { throw new InvalidOperationException($"Failed to load assembly and get function pointer. HRESULT: 0x{result:X8}"); } } finally { PlatformStringMarshaller.Free(assemblyPathNative); PlatformStringMarshaller.Free(typeNameNative); PlatformStringMarshaller.Free(methodNameNative); } var entryPoint = (delegate* unmanaged[Cdecl]<nint, int, int>)functionPointer; return entryPoint(0, 0); } /// <summary> /// Runs the managed application using the hostfxr command-line hosting path. /// This is the simplest way to invoke <c>dotnet.dll Program.Main(args)</c>. /// </summary> /// <param name="hostPath">Path to the host executable (e.g., dotnet.exe).</param> /// <param name="dotnetRoot">Path to the .NET installation root.</param> /// <param name="hostfxrPath">Path to the hostfxr library.</param> /// <param name="args">Command-line arguments (first element should be the app path).</param> /// <returns>The application exit code.</returns> public static int RunApp(string hostPath, string dotnetRoot, string hostfxrPath, string[] args) { var parameters = new Interop.hostfxr_initialize_parameters { size = sizeof(Interop.hostfxr_initialize_parameters), host_path = PlatformStringMarshaller.ConvertToUnmanaged(hostPath), dotnet_root = PlatformStringMarshaller.ConvertToUnmanaged(dotnetRoot), }; try { var _init = Activities.Source.StartActivity("hostfxr-initialization"); StatusCode result = Interop.hostfxr_initialize_for_dotnet_command_line( args.Length, args, in parameters, out nint handle); if (result != StatusCode.Success && handle == 0) { _init?.SetStatus(ActivityStatusCode.Error); _init?.Stop(); throw new InvalidOperationException($"hostfxr_initialize_for_dotnet_command_line failed. Status: {result} (0x{(uint)result:X8})"); } else { _init?.SetStatus(ActivityStatusCode.Ok); _init?.Stop(); } try { // Set HOSTFXR_PATH property to match the muxer's behavior for SDK commands. // The muxer sets this when is_sdk_command=true so the SDK can load hostfxr // without relying on dlopen/LoadLibrary to find it. if (!string.IsNullOrEmpty(hostfxrPath)) { StatusCode propertyResult = Interop.hostfxr_set_runtime_property_value( handle, NativeConstants.RuntimeProperty.HostFxrPath, hostfxrPath); if (propertyResult != StatusCode.Success) { throw new InvalidOperationException( $"hostfxr_set_runtime_property_value failed for {NativeConstants.RuntimeProperty.HostFxrPath}. Status: {propertyResult} (0x{(uint)propertyResult:X8})"); } } // Propagate OTel trace context to the managed CLI so that its spans // become children of the AOT-side main activity. if (Activity.Current is { } currentActivity) { string traceparent = $"00-{currentActivity.Context.TraceId}-{currentActivity.Context.SpanId}-{(currentActivity.Context.TraceFlags == ActivityTraceFlags.Recorded ? "01" : "00")}"; Interop.hostfxr_set_runtime_property_value(handle, Activities.TRACEPARENT, traceparent); if (!string.IsNullOrEmpty(currentActivity.Context.TraceState)) { Interop.hostfxr_set_runtime_property_value(handle, Activities.TRACESTATE, currentActivity.Context.TraceState); } } using var invoke = Activities.Source.StartActivity("coreclr-invocation"); StatusCode appResult = Interop.hostfxr_run_app(handle); return (int)appResult; } finally { Interop.hostfxr_close(handle); } } finally { PlatformStringMarshaller.Free(parameters.host_path); PlatformStringMarshaller.Free(parameters.dotnet_root); } } private void Initialize() { nint dotnetRootNative = PlatformStringMarshaller.ConvertToUnmanaged(_dotnetRoot); nint runtimeConfigPathNative = PlatformStringMarshaller.ConvertToUnmanaged(_runtimeConfigPath); try { var parameters = new Interop.hostfxr_initialize_parameters { size = sizeof(Interop.hostfxr_initialize_parameters), dotnet_root = dotnetRootNative, }; StatusCode result = Interop.hostfxr_initialize_for_runtime_config( runtimeConfigPathNative, in parameters, out _hostContextHandle); if (result != StatusCode.Success && _hostContextHandle == 0) { throw new InvalidOperationException($"hostfxr_initialize_for_runtime_config failed. Status: {result} (0x{(uint)result:X8})"); } nint loadAssemblyDelegate; result = Interop.hostfxr_get_runtime_delegate( _hostContextHandle, Interop.hostfxr_delegate_type.hdt_load_assembly_and_get_function_pointer, out loadAssemblyDelegate); if (result != StatusCode.Success) { Interop.hostfxr_close(_hostContextHandle); _hostContextHandle = 0; throw new InvalidOperationException($"hostfxr_get_runtime_delegate failed. Status: {result} (0x{(uint)result:X8})"); } _loadAssemblyAndGetFunctionPointer = (delegate* unmanaged[Cdecl]<nint, nint, nint, nint, nint, nint, int>)loadAssemblyDelegate; } finally { PlatformStringMarshaller.Free(dotnetRootNative); PlatformStringMarshaller.Free(runtimeConfigPathNative); } } public void Dispose() { if (!_disposed && _hostContextHandle != 0) { Interop.hostfxr_close(_hostContextHandle); _hostContextHandle = 0; _disposed = true; } } }