|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Runtime.CompilerServices;
#if NET
using System.Runtime.InteropServices.Marshalling;
#endif
namespace Microsoft.DotNet.NativeWrapper
{
public static partial class Interop
{
public static readonly bool RunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#if NET
private static readonly string? s_hostFxrPath;
#endif
static Interop()
{
#if NET
if (!RunningOnWindows)
{
s_hostFxrPath = (string)AppContext.GetData(Constants.RuntimeProperty.HostFxrPath)!;
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly())!.ResolvingUnmanagedDll += HostFxrResolver;
}
#else
PreloadWindowsLibrary(Constants.HostFxr);
#endif
}
#if NETFRAMEWORK
// MSBuild SDK resolvers are required to be AnyCPU, but we have a native dependency and .NET Framework does not
// have a built-in facility for dynamically loading user native dlls for the appropriate platform. We therefore
// preload the version with the correct architecture (from a corresponding sub-folder relative to us) on static
// construction so that subsequent P/Invokes can find it.
//
// An example path this is trying to resolve (when compiled into the MSBuildSdkResolver):
//
// C:\Program Files\Microsoft Visual Studio\18\Preview\MSBuild\Current\Bin\SdkResolvers\Microsoft.DotNet.MSBuildSdkResolver
//
// `hostfxr.dll` sits in subfolders there. This isn't necessary to do when running on .NET as the host will
// already be loaded in the process. Unfortunately there are no issues or pull requests tracking the original
// implementation of this as it was committed directly from a dev branch into main.
// lpFileName passed to LoadLibraryEx must be a full path.
private const int LOAD_WITH_ALTERED_SEARCH_PATH = 0x8;
private static void PreloadWindowsLibrary(string dllFileName)
{
string? basePath = Path.GetDirectoryName(typeof(Interop).Assembly.Location);
string architecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
string dllPath = Path.Combine(basePath ?? string.Empty, architecture, $"{dllFileName}.dll");
// return value is intentionally ignored as we let the subsequent P/Invokes fail naturally.
LoadLibraryExW(dllPath, IntPtr.Zero, LOAD_WITH_ALTERED_SEARCH_PATH);
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern IntPtr LoadLibraryExW(string lpFileName, nint hFile, int dwFlags);
#endif
#if NET
private static IntPtr HostFxrResolver(Assembly assembly, string libraryName)
{
if (libraryName != Constants.HostFxr)
{
return IntPtr.Zero;
}
if (string.IsNullOrEmpty(s_hostFxrPath))
{
throw new HostFxrRuntimePropertyNotSetException();
}
if (!NativeLibrary.TryLoad(s_hostFxrPath, out var handle))
{
throw new HostFxrNotFoundException(s_hostFxrPath);
}
return handle;
}
#endif
/// <summary>
/// Flags that control SDK resolution behavior in <see cref="hostfxr_resolve_sdk2"/>.
/// </summary>
[Flags]
internal enum hostfxr_resolve_sdk2_flags_t : int
{
/// <summary>
/// No special flags. Default resolution behavior.
/// </summary>
none = 0,
/// <summary>
/// Disallow resolution to return a prerelease SDK version unless a prerelease
/// version was explicitly specified via global.json.
/// </summary>
disallow_prerelease = 0x1
}
/// <summary>
/// Identifies the type of result returned through the <see cref="hostfxr_resolve_sdk2"/> callback.
/// </summary>
/// <remarks>
/// <para>
/// The callback may be invoked multiple times with different keys to provide
/// comprehensive information about the SDK resolution process.
/// </para>
/// </remarks>
internal enum hostfxr_resolve_sdk2_result_key_t : int
{
/// <summary>
/// The resolved SDK directory path. Only provided if resolution succeeds.
/// </summary>
resolved_sdk_dir = 0,
/// <summary>
/// The path to the global.json file that influenced resolution, if any.
/// Not provided if no global.json was found or if it didn't affect resolution.
/// </summary>
global_json_path = 1,
/// <summary>
/// The SDK version requested by global.json, if a specific version was specified.
/// Provided for both successful and failed resolutions.
/// </summary>
requested_version = 2,
/// <summary>
/// The state of global.json processing. One of: "not_found", "valid", "invalid_json", or "invalid_data".
/// </summary>
global_json_state = 3
}
/// <summary>
/// Contains comprehensive information about the .NET environment, passed to the
/// <c>hostfxr_get_dotnet_environment_info</c> callback.
/// </summary>
/// <remarks>
/// <para>
/// This structure provides information about the hostfxr version, all installed SDKs,
/// and all installed shared frameworks.
/// </para>
/// <para>
/// <b>Memory ownership:</b> This structure, the arrays it references, and all strings within those
/// arrays are owned by the native hostfxr library. They are valid only for the duration of the
/// callback invocation. The caller must copy any data it needs to retain. Do not attempt to free
/// any pointers in this structure or the nested structures.
/// </para>
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct hostfxr_dotnet_environment_info
{
/// <summary>The size of this structure in bytes.</summary>
public nint size;
/// <summary>Pointer to the hostfxr version string.</summary>
public PlatformString hostfxr_version;
/// <summary>Pointer to the hostfxr commit hash string.</summary>
public PlatformString hostfxr_commit_hash;
/// <summary>The number of SDKs in the <see cref="sdks"/> array.</summary>
public nint sdk_count;
/// <summary>Pointer to an array of <see cref="hostfxr_dotnet_environment_sdk_info"/> structures.</summary>
public hostfxr_dotnet_environment_sdk_info* sdks;
/// <summary>The number of frameworks in the <see cref="frameworks"/> array.</summary>
public nint framework_count;
/// <summary>Pointer to an array of <see cref="hostfxr_dotnet_environment_framework_info"/> structures.</summary>
public hostfxr_dotnet_environment_framework_info* frameworks;
}
/// <summary>
/// Contains information about an installed shared framework, returned by <see cref="hostfxr_get_dotnet_environment_info"/>.
/// </summary>
/// <remarks>
/// <para>
/// <b>Memory ownership:</b> This structure and all referenced strings are owned by the native hostfxr
/// library. They are valid only for the duration of the callback invocation. The caller must copy
/// any data it needs to retain. Do not attempt to free any pointers in this structure.
/// </para>
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct hostfxr_dotnet_environment_framework_info
{
/// <summary>The size of this structure in bytes.</summary>
public nint size;
/// <summary>Pointer to the framework name (e.g., "Microsoft.NETCore.App").</summary>
public PlatformString name;
/// <summary>Pointer to the framework version string (e.g., "8.0.0").</summary>
public PlatformString version;
/// <summary>Pointer to the full path to the framework directory.</summary>
public PlatformString path;
}
/// <summary>
/// Contains information about an installed SDK, returned by <see cref="hostfxr_dotnet_environment_info"/>.
/// </summary>
/// <remarks>
/// <para>
/// <b>Memory ownership:</b> This structure and all referenced strings are owned by the native hostfxr
/// library. They are valid only for the duration of the callback invocation. The caller must copy
/// any data it needs to retain. Do not attempt to free any pointers in this structure.
/// </para>
/// </remarks>
[StructLayout(LayoutKind.Sequential)]
internal struct hostfxr_dotnet_environment_sdk_info
{
/// <summary>The size of this structure in bytes.</summary>
public nint size;
/// <summary>Pointer to the SDK version string (e.g., "8.0.100").</summary>
public PlatformString version;
/// <summary>Pointer to the full path to the SDK directory.</summary>
public PlatformString path;
}
/// <summary>
/// Callback delegate for receiving environment information from <c>hostfxr_get_dotnet_environment_info</c>.
/// </summary>
/// <param name="info">Pointer to a <see cref="hostfxr_dotnet_environment_info"/> structure.</param>
/// <param name="result_context">The context pointer passed to the original function call.</param>
/// <remarks>
/// <para>
/// All data referenced by the info structure is valid only for the duration of the callback.
/// Copy any needed data before returning.
/// </para>
/// </remarks>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void hostfxr_get_dotnet_environment_info_result_fn(ref hostfxr_dotnet_environment_info info, nint result_context);
/// <summary>
/// Gets comprehensive information about the .NET environment including installed SDKs and frameworks.
/// </summary>
/// <param name="dotnetRoot">Path to the .NET installation root, or <c>null</c> to use the default or registered location.</param>
/// <param name="reserved">Reserved for future use. Must be zero.</param>
/// <param name="result">Callback invoked with environment information.</param>
/// <param name="resultContext">Arbitrary context pointer passed through to the callback.</param>
/// <returns>
/// <see cref="StatusCode.Success"/> on success, or an error status code.
/// </returns>
/// <remarks>
/// <para>
/// The callback receives a pointer to a <see cref="hostfxr_dotnet_environment_info"/> structure
/// containing hostfxr version information, installed SDKs, and installed frameworks.
/// All data is valid only for the duration of the callback.
/// </para>
/// </remarks>
#if NET
[LibraryImport(Constants.HostFxr, StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(PlatformStringMarshaller))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_get_dotnet_environment_info(
#else
[DllImport(Constants.HostFxr, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_get_dotnet_environment_info(
#endif
string? dotnetRoot,
nint reserved,
hostfxr_get_dotnet_environment_info_result_fn result,
nint resultContext);
/// <summary>
/// Callback delegate for receiving error messages from the hosting layer.
/// </summary>
/// <param name="message">The error message text.</param>
/// <remarks>
/// <para>
/// Set via <see cref="hostfxr_set_error_writer"/> to receive error messages that would
/// otherwise be written to stderr. Useful for capturing diagnostics in GUI applications
/// or custom logging scenarios.
/// </para>
/// </remarks>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void hostfxr_error_writer_fn(PlatformString message);
/// <summary>
/// Sets a callback for receiving error messages from the hosting layer.
/// </summary>
/// <param name="error_writer">
/// Callback to receive error messages, or <c>null</c> to restore the default behavior (writing to stderr).
/// </param>
/// <returns>
/// A pointer to the previously registered error writer callback, or zero if none was set.
/// </returns>
/// <remarks>
/// <para>
/// Use this to capture error messages in GUI applications or for custom logging scenarios
/// where stderr is not appropriate.
/// </para>
/// </remarks>
#if NET
[LibraryImport(Constants.HostFxr)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static unsafe partial delegate* unmanaged[Cdecl]<PlatformString, void> hostfxr_set_error_writer(
#else
[DllImport(Constants.HostFxr, CallingConvention = CallingConvention.Cdecl)]
internal static extern unsafe delegate* unmanaged[Cdecl]<PlatformString, void> hostfxr_set_error_writer(
#endif
delegate* unmanaged[Cdecl]<PlatformString, void> error_writer);
/// <summary>
/// Callback delegate for receiving SDK resolution results from <see cref="hostfxr_resolve_sdk2"/>.
/// </summary>
/// <param name="key">Identifies the type of data being provided.</param>
/// <param name="value">The string value associated with the key.</param>
/// <remarks>
/// <para>
/// This callback may be invoked multiple times with different keys during a single
/// resolution operation. The string values are valid only for the duration of the callback.
/// </para>
/// </remarks>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void hostfxr_resolve_sdk2_result_fn(hostfxr_resolve_sdk2_result_key_t key, PlatformString value);
/// <summary>
/// Resolves the SDK directory with detailed results provided via callback.
/// </summary>
/// <param name="exeDir">
/// Directory containing the dotnet executable, or <see langword="null"/>/empty for default search.
/// </param>
/// <param name="workingDir">
/// Directory to start searching for global.json, or <see langword="null"/>/empty to disable global.json search.
/// </param>
/// <param name="flags">
/// Resolution flags controlling behavior (e.g., <see cref="hostfxr_resolve_sdk2_flags_t.disallow_prerelease"/>).
/// </param>
/// <param name="result">Dictionary with resolution results.</param>
/// <returns>
/// <see cref="StatusCode.Success"/> if the SDK was resolved, or <see cref="StatusCode.SdkResolveFailure"/> if not found.
/// </returns>
#if NET
[LibraryImport(
Constants.HostFxr,
EntryPoint = "hostfxr_resolve_sdk2",
StringMarshalling = StringMarshalling.Custom,
StringMarshallingCustomType = typeof(PlatformStringMarshaller))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_resolve_sdk2(
#else
[DllImport(
Constants.HostFxr,
EntryPoint = "hostfxr_resolve_sdk2",
CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_resolve_sdk2(
#endif
string? exeDir,
string? workingDir,
hostfxr_resolve_sdk2_flags_t flags,
hostfxr_resolve_sdk2_result_fn result);
/// <summary>
/// Callback delegate for receiving available SDK list from <see cref="hostfxr_get_available_sdks"/>.
/// </summary>
/// <param name="sdk_count">The number of SDKs in the array.</param>
/// <param name="sdk_dirs">Pointer to an array of pointers to null-terminated SDK directory path strings.</param>
/// <remarks>
/// <para>
/// The SDKs are returned in ascending version order. The array and string data are valid
/// only for the duration of the callback.
/// </para>
/// </remarks>
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void hostfxr_get_available_sdks_result_fn(int sdk_count, nint sdk_dirs);
/// <summary>
/// Gets all installed SDKs in ascending version order.
/// </summary>
/// <param name="exeDir">Path to the dotnet executable directory.</param>
/// <param name="result">List of SDK directories.</param>
/// <returns>
/// Always returns <see cref="StatusCode.Success"/>.
/// </returns>
/// <remarks>
/// <para>
/// The SDKs are returned in ascending version order. This is useful for the MSBuild SDK resolver
/// to find a compatible SDK when the latest SDK is incompatible.
/// </para>
/// </remarks>
internal static StatusCode hostfxr_get_available_sdks(string? exeDir, out string[] result)
{
string[]? localResult = null;
StatusCode status = hostfxr_get_available_sdks_private(exeDir, SdkCallback);
result = localResult ?? [];
return status;
unsafe void SdkCallback(int sdk_count, nint sdk_dirs)
{
localResult = new string[sdk_count];
for (int i = 0; i < sdk_count; i++)
{
localResult[i] = ((PlatformString*)sdk_dirs)[i];
}
}
}
#if NET
[LibraryImport(
Constants.HostFxr,
EntryPoint = "hostfxr_get_available_sdks",
StringMarshalling = StringMarshalling.Custom,
StringMarshallingCustomType = typeof(PlatformStringMarshaller))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
private static partial StatusCode hostfxr_get_available_sdks_private(
#else
[DllImport(
Constants.HostFxr,
EntryPoint = "hostfxr_get_available_sdks",
CharSet = CharSet.Unicode,
CallingConvention = CallingConvention.Cdecl)]
private static extern StatusCode hostfxr_get_available_sdks_private(
#endif
string? exeDir,
hostfxr_get_available_sdks_result_fn result);
/// <summary>
/// Delegate type identifiers for <see cref="hostfxr_get_runtime_delegate"/>.
/// </summary>
internal enum hostfxr_delegate_type
{
hdt_com_activation = 0,
hdt_load_in_memory_assembly = 1,
hdt_winrt_activation = 2,
hdt_com_register = 3,
hdt_com_unregister = 4,
hdt_load_assembly_and_get_function_pointer = 5,
hdt_get_function_pointer = 6,
hdt_load_assembly = 7,
hdt_load_assembly_bytes = 8,
}
/// <summary>
/// Parameters for <see cref="hostfxr_initialize_for_runtime_config"/> and
/// <see cref="hostfxr_initialize_for_dotnet_command_line"/>.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct hostfxr_initialize_parameters
{
public nint size;
public nint host_path;
public nint dotnet_root;
}
/// <summary>
/// Initializes the hosting context from a runtime configuration file.
/// </summary>
#if NET
[LibraryImport(Constants.HostFxr)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_initialize_for_runtime_config(
#else
[DllImport(Constants.HostFxr, CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_initialize_for_runtime_config(
#endif
nint runtimeConfigPath,
in hostfxr_initialize_parameters parameters,
out nint hostContextHandle);
/// <summary>
/// Gets a runtime delegate of the specified type.
/// </summary>
#if NET
[LibraryImport(Constants.HostFxr)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_get_runtime_delegate(
#else
[DllImport(Constants.HostFxr, CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_get_runtime_delegate(
#endif
nint hostContextHandle,
hostfxr_delegate_type type,
out nint @delegate);
/// <summary>
/// Initializes the hosting context for running a dotnet command line application.
/// </summary>
#if NET
[LibraryImport(Constants.HostFxr,
StringMarshalling = StringMarshalling.Custom,
StringMarshallingCustomType = typeof(PlatformStringMarshaller))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_initialize_for_dotnet_command_line(
#else
[DllImport(Constants.HostFxr, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_initialize_for_dotnet_command_line(
#endif
int argc,
#if NET
[MarshalUsing(typeof(ArrayMarshaller<string, nint>))]
[MarshalUsing(typeof(PlatformStringMarshaller), ElementIndirectionDepth = 1)]
#else
[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)]
#endif
[In] string[] argv,
in hostfxr_initialize_parameters parameters,
out nint hostContextHandle);
/// <summary>
/// Runs the application initialized by <see cref="hostfxr_initialize_for_dotnet_command_line"/>.
/// </summary>
#if NET
[LibraryImport(Constants.HostFxr)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_run_app(
#else
[DllImport(Constants.HostFxr, CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_run_app(
#endif
nint hostContextHandle);
/// <summary>
/// Closes the hosting context handle and releases associated resources.
/// </summary>
#if NET
[LibraryImport(Constants.HostFxr)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_close(
#else
[DllImport(Constants.HostFxr, CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_close(
#endif
nint hostContextHandle);
/// <summary>
/// Sets a runtime property value. Must be called before the runtime is loaded.
/// </summary>
/// <param name="hostContextHandle">Handle from initialization. The runtime must not be loaded yet.</param>
/// <param name="name">Name of the property to set.</param>
/// <param name="value">Value to set, or <see langword="null"/> to remove the property.</param>
/// <returns>
/// <see cref="StatusCode.Success"/> on success, <see cref="StatusCode.InvalidArgFailure"/> if the runtime
/// is already loaded, or another error status code.
/// </returns>
/// <remarks>
/// <para>
/// Properties set through this API are passed to the runtime during initialization and can affect
/// runtime behavior. Once the runtime is loaded, properties become read-only.
/// </para>
/// </remarks>
#if NET
[LibraryImport(
Constants.HostFxr,
StringMarshalling = StringMarshalling.Custom,
StringMarshallingCustomType = typeof(PlatformStringMarshaller))]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
internal static partial StatusCode hostfxr_set_runtime_property_value(
#else
[DllImport(Constants.HostFxr, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
internal static extern StatusCode hostfxr_set_runtime_property_value(
#endif
nint hostContextHandle,
string name,
string? value);
}
}
|