diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 342668146a873..adf2908c581ab 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -117,7 +117,7 @@ - + @@ -285,10 +285,12 @@ + + Common\Interop\Windows\OleAut32\Interop.VariantClear.cs diff --git a/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml b/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml index 1b4e3a1bd2acd..e3682b253e3a7 100644 --- a/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml +++ b/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml @@ -21,13 +21,6 @@ - - - - - diff --git a/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Windows.xml b/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Windows.xml index 52ab4af9bef74..7ddddc6f48af8 100644 --- a/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Windows.xml +++ b/src/coreclr/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Windows.xml @@ -10,5 +10,19 @@ + + + + + + + + + + + + diff --git a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.CoreCLR.cs new file mode 100644 index 0000000000000..3e89bca82e0f2 --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.CoreCLR.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace Internal.Runtime.InteropServices +{ + public static partial class ComponentActivator + { + // This hook for when GetFunctionPointer is called when the feature is disabled allows us to + // provide error messages for known hosting scenarios such as C++/CLI. + private static void OnDisabledGetFunctionPointerCall(IntPtr typeNameNative, IntPtr methodNameNative) + { + if (!OperatingSystem.IsWindows()) + return; + + // Check for the exact type and method name used by ijwhost - see src/native/corehost/ijwhost/ijwhost.cpp + if (Marshal.PtrToStringUni(methodNameNative) == "LoadInMemoryAssemblyInContext" + && Marshal.PtrToStringUni(typeNameNative) == $"Internal.Runtime.InteropServices.{nameof(InMemoryAssemblyLoader)}, {CoreLib.Name}") + { + throw new NotSupportedException(SR.NotSupported_CppCli); + } + } + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.PlatformNotSupported.cs b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.PlatformNotSupported.cs new file mode 100644 index 0000000000000..1a0881cd414fe --- /dev/null +++ b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.PlatformNotSupported.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Loader; + +namespace Internal.Runtime.InteropServices +{ + /// + /// This class enables the .NET IJW host to load an in-memory module as a .NET assembly + /// + public static class InMemoryAssemblyLoader + { + /// + /// Loads into an isolated AssemblyLoadContext an assembly that has already been loaded into memory by the OS loader as a native module. + /// + /// The native module handle for the assembly. + /// The path to the assembly (as a pointer to a UTF-16 C string). + public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath) + => throw new PlatformNotSupportedException(); + } +} diff --git a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs index 4456ce1f9ae14..fa7414da21d53 100644 --- a/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs +++ b/src/coreclr/System.Private.CoreLib/src/Internal/Runtime/InteropServices/InMemoryAssemblyLoader.cs @@ -2,48 +2,87 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Runtime.Loader; +using System.Runtime.Versioning; namespace Internal.Runtime.InteropServices { /// /// This class enables the .NET IJW host to load an in-memory module as a .NET assembly /// + [SupportedOSPlatform("windows")] public static class InMemoryAssemblyLoader { -#if TARGET_WINDOWS private static bool IsSupported { get; } = InitializeIsSupported(); - private static bool InitializeIsSupported() => AppContext.TryGetSwitch("System.Runtime.InteropServices.EnableCppCLIHostActivation", out bool isSupported) ? isSupported : true; -#endif /// - /// Loads into an isolated AssemblyLoadContext an assembly that has already been loaded into memory by the OS loader as a native module. + /// Loads an assembly that has already been loaded into memory by the OS loader as a native module + /// into an isolated AssemblyLoadContext. /// /// The native module handle for the assembly. /// The path to the assembly (as a pointer to a UTF-16 C string). public static unsafe void LoadInMemoryAssembly(IntPtr moduleHandle, IntPtr assemblyPath) { -#if TARGET_WINDOWS if (!IsSupported) - throw new NotSupportedException("This API is not enabled in trimmed scenarios. see https://aka.ms/dotnet-illink/nativehost for more details"); + throw new NotSupportedException(SR.NotSupported_CppCli); + +#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml + LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath); +#pragma warning restore IL2026 + } + + /// + /// Loads into an assembly that has already been loaded into memory by the OS loader as a native module + /// into the specified load context. + /// + /// The native module handle for the assembly. + /// The path to the assembly (as a pointer to a UTF-16 C string). + /// Load context (currently must be IntPtr.Zero) + [UnmanagedCallersOnly] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "The same C++/CLI feature switch applies to LoadInMemoryAssembly and this function. We rely on the warning from LoadInMemoryAssembly.")] + public static unsafe void LoadInMemoryAssemblyInContext(IntPtr moduleHandle, IntPtr assemblyPath, IntPtr loadContext) + { + if (!IsSupported) + throw new NotSupportedException(SR.NotSupported_CppCli); + + if (loadContext != IntPtr.Zero) + throw new ArgumentOutOfRangeException(nameof(loadContext)); + LoadInMemoryAssemblyInContextImpl(moduleHandle, assemblyPath, AssemblyLoadContext.Default); + } + + [RequiresUnreferencedCode("C++/CLI is not trim-compatible", Url = "https://aka.ms/dotnet-illink/nativehost")] + private static void LoadInMemoryAssemblyInContextImpl(IntPtr moduleHandle, IntPtr assemblyPath, AssemblyLoadContext? alc = null) + { string? assemblyPathString = Marshal.PtrToStringUni(assemblyPath); if (assemblyPathString == null) - { throw new ArgumentOutOfRangeException(nameof(assemblyPath)); - } - // We don't cache the ALCs here since each IJW assembly will call this method at most once + // We don't cache the ALCs or resolvers here since each IJW assembly will call this method at most once // (the load process rewrites the stubs that call here to call the actual methods they're supposed to) -#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml - AssemblyLoadContext context = new IsolatedComponentLoadContext(assemblyPathString); -#pragma warning restore IL2026 - context.LoadFromInMemoryModule(moduleHandle); -#else - throw new PlatformNotSupportedException(); -#endif + if (alc is null) + { + alc = new IsolatedComponentLoadContext(assemblyPathString); + } + else if (alc == AssemblyLoadContext.Default) + { + var resolver = new AssemblyDependencyResolver(assemblyPathString); + AssemblyLoadContext.Default.Resolving += + [RequiresUnreferencedCode("C++/CLI is not trim-compatible", Url = "https://aka.ms/dotnet-illink/nativehost")] + (context, assemblyName) => + { + string? assemblyPath = resolver.ResolveAssemblyToPath(assemblyName); + return assemblyPath != null + ? context.LoadFromAssemblyPath(assemblyPath) + : null; + }; + } + + alc.LoadFromInMemoryModule(moduleHandle); } } } diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs index 46eb6c6585a3d..99b22c870eba0 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/Ijwhost.cs @@ -35,7 +35,7 @@ public void LoadLibrary() result.Should().Pass() .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext"); + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext"); } [Theory] @@ -57,7 +57,7 @@ public void ManagedHost(bool selfContained) result.Should().Pass() .And.HaveStdOutContaining("[C++/CLI] NativeEntryPoint: calling managed class") - .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"IsolatedComponentLoadContext") + .And.HaveStdOutContaining("[C++/CLI] ManagedClass: AssemblyLoadContext = \"Default\" System.Runtime.Loader.DefaultAssemblyLoadContext") .And.HaveStdErrContaining($"Executing as a {(selfContained ? "self-contained" : "framework-dependent")} app"); } diff --git a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml index 3def281c2bcab..27d64e4c5bf8f 100644 --- a/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml +++ b/src/libraries/System.Private.CoreLib/src/ILLink/ILLink.Descriptors.Shared.xml @@ -16,10 +16,16 @@ --> + + + + + + - + @@ -69,7 +75,7 @@ - + diff --git a/src/libraries/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs b/src/libraries/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs index 97f8c64008371..118afb963cfa6 100644 --- a/src/libraries/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs +++ b/src/libraries/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs @@ -11,7 +11,7 @@ namespace Internal.Runtime.InteropServices { - public static class ComponentActivator + public static partial class ComponentActivator { private const string TrimIncompatibleWarningMessage = "Native hosting is not trim compatible and this warning will be seen if trimming is enabled."; private const string NativeAOTIncompatibleWarningMessage = "The native code for the method requested might not be available at runtime."; @@ -115,7 +115,24 @@ public static unsafe int GetFunctionPointer(IntPtr typeNameNative, IntPtr functionHandle) { if (!IsSupported) + { +#if CORECLR + try + { + OnDisabledGetFunctionPointerCall(typeNameNative, methodNameNative); + } + catch (Exception e) + { + // The callback can intentionally throw NotSupportedException to provide errors to consumers, + // so we let that one through. Any other exceptions must not be leaked out. + if (e is NotSupportedException) + throw; + + return e.HResult; + } +#endif return HostFeatureDisabled; + } try { diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 0896ffcf6cc99..d3505fea8c957 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3727,6 +3727,9 @@ Built-in COM has been disabled via a feature switch. See https://aka.ms/dotnet-illink/com for more information. + + C++/CLI activation has been disabled via a feature switch. See https://aka.ms/dotnet-illink/nativehost for more information. + Queue empty. diff --git a/src/native/corehost/ijwhost/ijwhost.cpp b/src/native/corehost/ijwhost/ijwhost.cpp index 85020baba02db..ab4f047ee8123 100644 --- a/src/native/corehost/ijwhost/ijwhost.cpp +++ b/src/native/corehost/ijwhost/ijwhost.cpp @@ -2,15 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. #include "ijwhost.h" -#include "hostfxr.h" -#include "fxr_resolver.h" -#include "pal.h" -#include "trace.h" -#include "error_codes.h" -#include "utils.h" +#include +#include +#include +#include +#include +#include +#include #include "bootstrap_thunk.h" - #if defined(_WIN32) // IJW entry points are defined without the __declspec(dllexport) attribute. // The issue here is that the MSVC compiler links to the exact name _CorDllMain instead of their stdcall-managled names. @@ -22,8 +22,9 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate) { - return load_fxr_and_get_delegate( - hostfxr_delegate_type::hdt_load_in_memory_assembly, + get_function_pointer_fn get_function_pointer; + int status = load_fxr_and_get_delegate( + hostfxr_delegate_type::hdt_get_function_pointer, [handle](const pal::string_t& host_path, pal::string_t* config_path_out) { pal::string_t mod_path; @@ -39,8 +40,18 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m return StatusCode::Success; }, - delegate + &get_function_pointer ); + if (status != StatusCode::Success) + return status; + + return get_function_pointer( + _X("Internal.Runtime.InteropServices.InMemoryAssemblyLoader, System.Private.CoreLib"), + _X("LoadInMemoryAssemblyInContext"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, // load context + nullptr, // reserved + (void**)delegate); } IJW_API BOOL STDMETHODCALLTYPE _CorDllMain(HINSTANCE hInst, diff --git a/src/native/corehost/ijwhost/ijwhost.h b/src/native/corehost/ijwhost/ijwhost.h index 78a242f0dbd42..c3698a4d4acb6 100644 --- a/src/native/corehost/ijwhost/ijwhost.h +++ b/src/native/corehost/ijwhost/ijwhost.h @@ -13,7 +13,7 @@ bool patch_vtable_entries(PEDecoder& decoder); void release_bootstrap_thunks(PEDecoder& decoder); bool are_thunks_installed_for_module(HMODULE instance); -using load_in_memory_assembly_fn = void(STDMETHODCALLTYPE*)(pal::dll_t handle, const pal::char_t* path); +using load_in_memory_assembly_fn = void(STDMETHODCALLTYPE*)(pal::dll_t handle, const pal::char_t* path, void* load_context); pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_memory_assembly_fn* delegate); diff --git a/src/native/corehost/ijwhost/ijwthunk.cpp b/src/native/corehost/ijwhost/ijwthunk.cpp index 91a462dfb102e..eeddf10fd931c 100644 --- a/src/native/corehost/ijwhost/ijwthunk.cpp +++ b/src/native/corehost/ijwhost/ijwthunk.cpp @@ -34,7 +34,7 @@ namespace HANDLE g_heapHandle; bool patch_vtable_entries(PEDecoder& pe) -{ +{ size_t numFixupRecords; IMAGE_COR_VTABLEFIXUP* pFixupTable = pe.GetVTableFixups(&numFixupRecords); @@ -68,7 +68,7 @@ bool patch_vtable_entries(PEDecoder& pe) } trace::setup(); - + error_writer_scope_t writer_scope(swallow_trace); size_t currentThunk = 0; @@ -115,7 +115,7 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui { trace::setup(); error_writer_scope_t writer_scope(swallow_trace); - + bootstrap_thunk *pThunk = bootstrap_thunk::get_thunk_from_cookie(cookie); load_in_memory_assembly_fn loadInMemoryAssembly; pal::dll_t moduleHandle = pThunk->get_dll_handle(); @@ -128,7 +128,7 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui // As we were taken here via an entry point with arbitrary signature, // there's no way of returning the error code so we just throw it. - trace::error(_X("Failed to start the .NET runtime. Error code %d"), status); + trace::error(_X("Failed to start the .NET runtime. Error code: %#x"), status); #pragma warning (push) #pragma warning (disable: 4297) @@ -145,7 +145,7 @@ extern "C" std::uintptr_t __stdcall start_runtime_and_get_target_address(std::ui #pragma warning (pop) } - loadInMemoryAssembly(moduleHandle, app_path.c_str()); + loadInMemoryAssembly(moduleHandle, app_path.c_str(), nullptr); std::uintptr_t thunkAddress = *(pThunk->get_slot_address());