diff --git a/src/coreclr/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs b/src/coreclr/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs index fedf151a7ba5ae..023a96b5576588 100644 --- a/src/coreclr/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs +++ b/src/coreclr/src/System.Private.CoreLib/src/Internal/Runtime/InteropServices/ComponentActivator.cs @@ -77,6 +77,55 @@ public static unsafe int LoadAssemblyAndGetFunctionPointer(IntPtr assemblyPathNa return 0; } + /// + /// Native hosting entry point for creating a native delegate + /// + /// Assembly qualified type name + /// Public static method name compatible with delegateType + /// Assembly qualified delegate type name + /// Extensibility parameter (currently unused) + /// Extensibility parameter (currently unused) + /// Pointer where to store the function pointer result + [UnmanagedCallersOnly] + public static unsafe int GetFunctionPointer(IntPtr typeNameNative, + IntPtr methodNameNative, + IntPtr delegateTypeNative, + IntPtr loadContext, + IntPtr reserved, + IntPtr functionHandle) + { + try + { + // Validate all parameters first. + string typeName = MarshalToString(typeNameNative, nameof(typeNameNative)); + string methodName = MarshalToString(methodNameNative, nameof(methodNameNative)); + + if (loadContext != IntPtr.Zero) + { + throw new ArgumentOutOfRangeException(nameof(loadContext)); + } + + if (reserved != IntPtr.Zero) + { + throw new ArgumentOutOfRangeException(nameof(reserved)); + } + + if (functionHandle == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(functionHandle)); + } + + // Create the function pointer. + *(IntPtr*)functionHandle = InternalGetFunctionPointer(AssemblyLoadContext.Default, typeName, methodName, delegateTypeNative); + } + catch (Exception e) + { + return e.HResult; + } + + return 0; + } + private static IsolatedComponentLoadContext GetIsolatedComponentLoadContext(string assemblyPath) { IsolatedComponentLoadContext? alc; diff --git a/src/installer/corehost/cli/coreclr_delegates.h b/src/installer/corehost/cli/coreclr_delegates.h index 7e0dd1ac92f393..ec1e67d8fe8efc 100644 --- a/src/installer/corehost/cli/coreclr_delegates.h +++ b/src/installer/corehost/cli/coreclr_delegates.h @@ -35,4 +35,14 @@ typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_and_get_function_pointer_f // Signature of delegate returned by load_assembly_and_get_function_pointer_fn when delegate_type_name == null (default) typedef int (CORECLR_DELEGATE_CALLTYPE *component_entry_point_fn)(void *arg, int32_t arg_size_in_bytes); +typedef int (CORECLR_DELEGATE_CALLTYPE *get_function_pointer_fn)( + const char_t *type_name /* Assembly qualified type name */, + const char_t *method_name /* Public static method name compatible with delegateType */, + const char_t *delegate_type_name /* Assembly qualified delegate type name or null, + or UNMANAGEDCALLERSONLY_METHOD if the method is marked with + the UnmanagedCallersOnlyAttribute. */, + void *load_context /* Extensibility parameter (currently unused and must be 0) */, + void *reserved /* Extensibility parameter (currently unused and must be 0) */, + /*out*/ void **delegate /* Pointer where to store the function pointer result */); + #endif // __CORECLR_DELEGATES_H__ \ No newline at end of file diff --git a/src/installer/corehost/cli/corehost_context_contract.h b/src/installer/corehost/cli/corehost_context_contract.h index d5eb41a39c9e9c..718c937f619174 100644 --- a/src/installer/corehost/cli/corehost_context_contract.h +++ b/src/installer/corehost/cli/corehost_context_contract.h @@ -28,6 +28,7 @@ enum class coreclr_delegate_type com_register, com_unregister, load_assembly_and_get_function_pointer, + get_function_pointer, __last, // Sentinel value for determining the last known delegate type }; diff --git a/src/installer/corehost/cli/fxr/hostfxr.cpp b/src/installer/corehost/cli/fxr/hostfxr.cpp index da96eb0122d813..cbb516d2c3fefa 100644 --- a/src/installer/corehost/cli/fxr/hostfxr.cpp +++ b/src/installer/corehost/cli/fxr/hostfxr.cpp @@ -617,6 +617,8 @@ namespace return coreclr_delegate_type::com_unregister; case hostfxr_delegate_type::hdt_load_assembly_and_get_function_pointer: return coreclr_delegate_type::load_assembly_and_get_function_pointer; + case hostfxr_delegate_type::hdt_get_function_pointer: + return coreclr_delegate_type::get_function_pointer; } return coreclr_delegate_type::invalid; } @@ -641,6 +643,7 @@ namespace // If the host_context_handle was initialized using hostfxr_initialize_for_dotnet_command_line, // then only the following delegate types are currently supported: // hdt_load_assembly_and_get_function_pointer +// hdt_get_function_pointer // SHARED_API int32_t HOSTFXR_CALLTYPE hostfxr_get_runtime_delegate( const hostfxr_handle host_context_handle, diff --git a/src/installer/corehost/cli/hostfxr.h b/src/installer/corehost/cli/hostfxr.h index f0bc0a53e22088..acc5c9d8e11fd5 100644 --- a/src/installer/corehost/cli/hostfxr.h +++ b/src/installer/corehost/cli/hostfxr.h @@ -27,7 +27,8 @@ enum hostfxr_delegate_type hdt_winrt_activation, hdt_com_register, hdt_com_unregister, - hdt_load_assembly_and_get_function_pointer + hdt_load_assembly_and_get_function_pointer, + hdt_get_function_pointer, }; typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv); diff --git a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp index 163ccda96b3e9e..c06b36d705a33e 100644 --- a/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp +++ b/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp @@ -496,6 +496,12 @@ namespace "Internal.Runtime.InteropServices.ComponentActivator", "LoadAssemblyAndGetFunctionPointer", delegate); + case coreclr_delegate_type::get_function_pointer: + return coreclr->create_delegate( + "System.Private.CoreLib", + "Internal.Runtime.InteropServices.ComponentActivator", + "GetFunctionPointer", + delegate); default: return StatusCode::LibHostInvalidArgs; } diff --git a/src/installer/corehost/cli/test/nativehost/host_context_test.cpp b/src/installer/corehost/cli/test/nativehost/host_context_test.cpp index cf52fff4ccf075..5f791c89e5060e 100644 --- a/src/installer/corehost/cli/test/nativehost/host_context_test.cpp +++ b/src/installer/corehost/cli/test/nativehost/host_context_test.cpp @@ -294,6 +294,46 @@ namespace return rc; } + int call_get_function_pointer_flavour( + get_function_pointer_fn delegate, + const pal::char_t *type_name, + const pal::char_t *method_name, + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) + { + const pal::char_t *delegate_name = nullptr; + pal::string_t method_name_local{ method_name }; + if (pal::string_t::npos != method_name_local.find(_X("Unmanaged"))) + delegate_name = UNMANAGEDCALLERSONLY_METHOD; + + test_output << log_prefix << _X("calling get_function_pointer(\"") + << type_name << _X("\", \"") + << method_name << _X("\", ") + << to_printable_delegate_name(delegate_name) << _X(", ") + << _X("nullptr, nullptr, &functionPointerDelegate)") + << std::endl; + + component_entry_point_fn functionPointerDelegate = nullptr; + int rc = delegate(type_name, + method_name, + delegate_name, + nullptr /* reserved */, + nullptr /* reserved */, + (void **)&functionPointerDelegate); + + if (rc != StatusCode::Success) + { + test_output << log_prefix << _X("get_function_pointer failed: ") << std::hex << std::showbase << rc << std::endl; + } + else + { + test_output << log_prefix << _X("get_function_pointer succeeded: ") << std::hex << std::showbase << rc << std::endl; + rc = call_delegate_with_try_except(functionPointerDelegate, method_name, log_prefix, test_output); + } + + return rc; + } + bool component_load_assembly_and_get_function_pointer_test( const hostfxr_exports &hostfxr, const pal::char_t *config_path, @@ -380,6 +420,91 @@ namespace return rc == StatusCode::Success && rcClose == StatusCode::Success; } + + bool component_get_function_pointer_test( + const hostfxr_exports &hostfxr, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[], + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) + { + hostfxr_handle handle; + int rc = hostfxr.init_config(config_path, nullptr, &handle); + if (!STATUS_CODE_SUCCEEDED(rc)) + { + test_output << log_prefix << _X("hostfxr_initialize_for_runtime_config failed: ") << std::hex << std::showbase << rc << std::endl; + return false; + } + + test_output << log_prefix << _X("hostfxr_initialize_for_runtime_config succeeded: ") << std::hex << std::showbase << rc << std::endl; + + for (int i = 0; i <= argc - 2; i += 2) + { + const pal::char_t *type_name = argv[i]; + const pal::char_t *method_name = argv[i + 1]; + + get_function_pointer_fn delegate = nullptr; + rc = hostfxr.get_delegate(handle, hostfxr_delegate_type::hdt_get_function_pointer, (void **)&delegate); + if (rc != StatusCode::Success) + { + test_output << log_prefix << _X("hostfxr_get_runtime_delegate failed: ") << std::hex << std::showbase << rc << std::endl; + } + else + { + test_output << log_prefix << _X("hostfxr_get_runtime_delegate succeeded: ") << std::hex << std::showbase << rc << std::endl; + rc = call_get_function_pointer_flavour(delegate, type_name, method_name, log_prefix, test_output); + } + } + + int rcClose = hostfxr.close(handle); + if (rcClose != StatusCode::Success) + test_output << log_prefix << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc << std::endl; + + return rc == StatusCode::Success && rcClose == StatusCode::Success; + } + + bool app_get_function_pointer_test( + const hostfxr_exports &hostfxr, + int argc, + const pal::char_t *argv[], + const pal::char_t *log_prefix, + pal::stringstream_t &test_output) + { + hostfxr_handle handle; + int rc = hostfxr.init_command_line(argc, argv, nullptr, &handle); + if (rc != StatusCode::Success) + { + test_output << _X("hostfxr_initialize_for_command_line failed: ") << std::hex << std::showbase << rc << std::endl; + return false; + } + + test_output << log_prefix << _X("hostfxr_initialize_for_command_line succeeded: ") << std::hex << std::showbase << rc << std::endl; + + for (int i = 1; i <= argc - 2; i += 2) + { + const pal::char_t *type_name = argv[i]; + const pal::char_t *method_name = argv[i + 1]; + + get_function_pointer_fn delegate = nullptr; + rc = hostfxr.get_delegate(handle, hostfxr_delegate_type::hdt_get_function_pointer, (void **)&delegate); + if (rc != StatusCode::Success) + { + test_output << log_prefix << _X("hostfxr_get_runtime_delegate failed: ") << std::hex << std::showbase << rc << std::endl; + } + else + { + test_output << log_prefix << _X("hostfxr_get_runtime_delegate succeeded: ") << std::hex << std::showbase << rc << std::endl; + rc = call_get_function_pointer_flavour(delegate, type_name, method_name, log_prefix, test_output); + } + } + + int rcClose = hostfxr.close(handle); + if (rcClose != StatusCode::Success) + test_output << log_prefix << _X("hostfxr_close failed: ") << std::hex << std::showbase << rc << std::endl; + + return rc == StatusCode::Success && rcClose == StatusCode::Success; + } } host_context_test::check_properties host_context_test::check_properties_from_string(const pal::char_t *str) @@ -625,3 +750,26 @@ bool host_context_test::app_load_assembly_and_get_function_pointer( return app_load_assembly_and_get_function_pointer_test(hostfxr, argc, argv, config_log_prefix, test_output); } + +bool host_context_test::component_get_function_pointer( + const pal::string_t &hostfxr_path, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[], + pal::stringstream_t &test_output) +{ + hostfxr_exports hostfxr{ hostfxr_path }; + + return component_get_function_pointer_test(hostfxr, config_path, argc, argv, config_log_prefix, test_output); +} + +bool host_context_test::app_get_function_pointer( + const pal::string_t &hostfxr_path, + int argc, + const pal::char_t *argv[], + pal::stringstream_t &test_output) +{ + hostfxr_exports hostfxr{ hostfxr_path }; + + return app_get_function_pointer_test(hostfxr, argc, argv, config_log_prefix, test_output); +} diff --git a/src/installer/corehost/cli/test/nativehost/host_context_test.h b/src/installer/corehost/cli/test/nativehost/host_context_test.h index 1565dbaa7f8467..978e736d606cc6 100644 --- a/src/installer/corehost/cli/test/nativehost/host_context_test.h +++ b/src/installer/corehost/cli/test/nativehost/host_context_test.h @@ -70,4 +70,15 @@ namespace host_context_test int argc, const pal::char_t *argv[], pal::stringstream_t &test_output); + bool component_get_function_pointer( + const pal::string_t &hostfxr_path, + const pal::char_t *config_path, + int argc, + const pal::char_t *argv[], + pal::stringstream_t &test_output); + bool app_get_function_pointer( + const pal::string_t &hostfxr_path, + int argc, + const pal::char_t *argv[], + pal::stringstream_t &test_output); } diff --git a/src/installer/corehost/cli/test/nativehost/nativehost.cpp b/src/installer/corehost/cli/test/nativehost/nativehost.cpp index 92d8e8d2db39bf..9bcf3a2cb45f55 100644 --- a/src/installer/corehost/cli/test/nativehost/nativehost.cpp +++ b/src/installer/corehost/cli/test/nativehost/nativehost.cpp @@ -266,6 +266,57 @@ int main(const int argc, const pal::char_t *argv[]) std::cout << tostr(test_output.str()).data() << std::endl; return success ? EXIT_SUCCESS : EXIT_FAILURE; } + else if (pal::strcmp(command, _X("component_get_function_pointer")) == 0) + { + // args: ... [ ...] + const int min_argc = 4; + if (argc < min_argc + 2) + { + std::cerr << "Invalid arguments" << std::endl; + return -1; + } + + const pal::string_t hostfxr_path = argv[2]; + const pal::char_t *app_or_config_path = argv[3]; + + int remaining_argc = argc - min_argc; + const pal::char_t **remaining_argv = nullptr; + if (argc > min_argc) + remaining_argv = &argv[min_argc]; + + pal::stringstream_t test_output; + bool success = false; + + success = host_context_test::component_get_function_pointer(hostfxr_path, app_or_config_path, remaining_argc, remaining_argv, test_output); + + std::cout << tostr(test_output.str()).data() << std::endl; + return success ? EXIT_SUCCESS : EXIT_FAILURE; + } + else if (pal::strcmp(command, _X("app_get_function_pointer")) == 0) + { + // args: ... [ ...] + const int min_argc = 3; + if (argc < min_argc + 3) + { + std::cerr << "Invalid arguments" << std::endl; + return -1; + } + + const pal::string_t hostfxr_path = argv[2]; + + int remaining_argc = argc - min_argc; + const pal::char_t **remaining_argv = nullptr; + if (argc > min_argc) + remaining_argv = &argv[min_argc]; + + pal::stringstream_t test_output; + bool success = false; + + success = host_context_test::app_get_function_pointer(hostfxr_path, remaining_argc, remaining_argv, test_output); + + std::cout << tostr(test_output.str()).data() << std::endl; + return success ? EXIT_SUCCESS : EXIT_FAILURE; + } else if (pal::strcmp(command, _X("run_app")) == 0) { // args: ... diff --git a/src/installer/tests/Assets/TestProjects/AppWithCustomEntryPoints/AppWithCustomEntryPoints.csproj b/src/installer/tests/Assets/TestProjects/AppWithCustomEntryPoints/AppWithCustomEntryPoints.csproj new file mode 100644 index 00000000000000..fd506fea04cc07 --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/AppWithCustomEntryPoints/AppWithCustomEntryPoints.csproj @@ -0,0 +1,10 @@ + + + + $(NetCoreAppCurrent) + Exe + $(TestTargetRid) + $(MNAVersion) + + + diff --git a/src/installer/tests/Assets/TestProjects/AppWithCustomEntryPoints/Program.cs b/src/installer/tests/Assets/TestProjects/AppWithCustomEntryPoints/Program.cs new file mode 100644 index 00000000000000..8b46ce6523acb8 --- /dev/null +++ b/src/installer/tests/Assets/TestProjects/AppWithCustomEntryPoints/Program.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace AppWithCustomEntryPoints +{ + public static class Program + { + public static void Main(string[] args) + { + } + + private static int functionPointerCallCount = 0; + private static int entryPoint1CallCount = 0; + private static int entryPoint2CallCount = 0; + private static int unmanagedEntryPoint1CallCount = 0; + + private static void PrintFunctionPointerCallLog(string name, IntPtr arg, int size) + { + Console.WriteLine($"Called {name}(0x{arg.ToString("x")}, {size}) - function pointer call count: {functionPointerCallCount}"); + } + + public static int FunctionPointerEntryPoint1(IntPtr arg, int size) + { + functionPointerCallCount++; + entryPoint1CallCount++; + PrintFunctionPointerCallLog(nameof(FunctionPointerEntryPoint1), arg, size); + return entryPoint1CallCount; + } + + public static int FunctionPointerEntryPoint2(IntPtr arg, int size) + { + functionPointerCallCount++; + entryPoint2CallCount++; + PrintFunctionPointerCallLog(nameof(FunctionPointerEntryPoint2), arg, size); + return entryPoint2CallCount; + } + + public static int ThrowException(IntPtr arg, int size) + { + functionPointerCallCount++; + PrintFunctionPointerCallLog(nameof(ThrowException), arg, size); + throw new InvalidOperationException(nameof(ThrowException)); + } + + [UnmanagedCallersOnly] + public static int UnmanagedFunctionPointerEntryPoint1(IntPtr arg, int size) + { + functionPointerCallCount++; + unmanagedEntryPoint1CallCount++; + PrintFunctionPointerCallLog(nameof(UnmanagedFunctionPointerEntryPoint1), arg, size); + return unmanagedEntryPoint1CallCount; + } + } +} diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/GetFunctionPointer.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/GetFunctionPointer.cs new file mode 100644 index 00000000000000..d73c085afbaf90 --- /dev/null +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/GetFunctionPointer.cs @@ -0,0 +1,289 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.DotNet.Cli.Build.Framework; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting +{ + public partial class GetFunctionPointer : IClassFixture + { + private const string ComponentGetFunctionPointerArg = "component_get_function_pointer"; + private const string AppGetFunctionPointerArg = "app_get_function_pointer"; + + private readonly SharedTestState sharedState; + + public GetFunctionPointer(SharedTestState sharedTestState) + { + sharedState = sharedTestState; + } + + [Theory] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + public void CallDelegateOnApplicationContext(bool validType, bool validMethod) + { + var appProject = sharedState.ApplicationFixture.TestProject; + string[] args = + { + AppGetFunctionPointerArg, + sharedState.HostFxrPath, + appProject.AppDll, + validType ? sharedState.FunctionPointerTypeName : $"Component.BadType, {appProject.AssemblyName}", + validMethod ? sharedState.FunctionPointerEntryPoint1 : "BadMethod", + }; + CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot) + .Execute(); + + result.Should() + .InitializeContextForApp(appProject.AppDll); + + if (validType && validMethod) + { + result.Should().Pass() + .And.ExecuteFunctionPointer(sharedState.FunctionPointerEntryPoint1, 1, 1); + } + else + { + result.Should().Fail(); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + public void CallDelegateOnComponentContext(bool validType, bool validMethod) + { + var componentProject = sharedState.ComponentWithNoDependenciesFixture.TestProject; + string[] args = + { + ComponentGetFunctionPointerArg, + sharedState.HostFxrPath, + componentProject.RuntimeConfigJson, + validType ? sharedState.ComponentTypeName : $"Component.BadType, {componentProject.AssemblyName}", + validMethod ? sharedState.ComponentEntryPoint1 : "BadMethod", + }; + CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot) + .Execute(); + + result.Should() + .InitializeContextForConfig(componentProject.RuntimeConfigJson); + + // This should fail even with the valid type and valid method, + // because the type is not resolvable from the default AssemblyLoadContext. + result.Should().Fail(); + } + + [Fact] + public void CallDelegateOnSelfContainedApplicationContext() + { + var appProject = sharedState.SelfContainedApplicationFixture.TestProject; + string[] args = + { + AppGetFunctionPointerArg, + appProject.HostFxrDll, + appProject.AppDll, + sharedState.FunctionPointerTypeName, + sharedState.FunctionPointerEntryPoint1, + }; + CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot) + .Execute(); + + result.Should() + .InitializeContextForApp(appProject.AppDll) + .And.Pass() + .And.ExecuteFunctionPointer(sharedState.FunctionPointerEntryPoint1, 1, 1); + } + + [Theory] + [InlineData(1, false)] + [InlineData(1, true)] + [InlineData(10, false)] + [InlineData(10, true)] + public void CallDelegateOnApplicationContext_MultipleEntryPoints(int callCount, bool callUnmanaged) + { + var appProject = sharedState.ApplicationFixture.TestProject; + string[] baseArgs = + { + AppGetFunctionPointerArg, + sharedState.HostFxrPath, + appProject.AppDll, + }; + + string functionPointer1Name = callUnmanaged ? sharedState.UnmanagedFunctionPointerEntryPoint1 : sharedState.FunctionPointerEntryPoint1; + string[] componentInfo = + { + // [Unmanaged]FunctionPointerEntryPoint1 + sharedState.FunctionPointerTypeName, + functionPointer1Name, + // FunctionPointerEntryPoint2 + sharedState.FunctionPointerTypeName, + sharedState.FunctionPointerEntryPoint2, + }; + + IEnumerable args = baseArgs; + for (int i = 0; i < callCount; ++i) + { + args = args.Concat(componentInfo); + } + + CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot) + .Execute(); + + result.Should().Pass() + .And.InitializeContextForApp(appProject.AppDll); + + for (int i = 1; i <= callCount; ++i) + { + result.Should() + .ExecuteFunctionPointer(functionPointer1Name, i * 2 - 1, i) + .And.ExecuteFunctionPointer(sharedState.FunctionPointerEntryPoint2, i * 2, i); + } + } + + [Theory] + [InlineData(1)] + [InlineData(10)] + public void CallDelegateOnApplicationContext_MultipleFunctionPointers(int callCount) + { + var appProject = sharedState.ApplicationFixture.TestProject; + string[] baseArgs = + { + AppGetFunctionPointerArg, + sharedState.HostFxrPath, + appProject.AppDll, + }; + string[] componentInfo = + { + // FunctionPointer + sharedState.FunctionPointerTypeName, + sharedState.FunctionPointerEntryPoint1, + // FunctionPointer copy + sharedState.FunctionPointerTypeName, + sharedState.FunctionPointerEntryPoint2, + }; + IEnumerable args = baseArgs; + for (int i = 0; i < callCount; ++i) + { + args = args.Concat(componentInfo); + } + + CommandResult result = sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot) + .Execute(); + + result.Should().Pass() + .And.InitializeContextForApp(appProject.AppDll); + + for (int i = 1; i <= callCount; ++i) + { + result.Should() + .ExecuteFunctionPointer(sharedState.FunctionPointerEntryPoint1, i * 2 - 1, i) + .And.ExecuteFunctionPointer(sharedState.FunctionPointerEntryPoint2, i * 2, i); + } + } + + [Fact] + public void CallDelegateOnApplicationContext_UnhandledException() + { + string entryPoint = "ThrowException"; + var appProject = sharedState.ApplicationFixture.TestProject; + string[] args = + { + AppGetFunctionPointerArg, + sharedState.HostFxrPath, + appProject.AppDll, + sharedState.FunctionPointerTypeName, + entryPoint, + }; + + sharedState.CreateNativeHostCommand(args, sharedState.DotNetRoot) + .Execute() + .Should().Fail() + .And.InitializeContextForApp(appProject.AppDll) + .And.ExecuteFunctionPointerWithException(entryPoint, 1); + } + + public class SharedTestState : SharedTestStateBase + { + public string HostFxrPath { get; } + public string DotNetRoot { get; } + + public TestProjectFixture ApplicationFixture { get; } + public TestProjectFixture ComponentWithNoDependenciesFixture { get; } + public TestProjectFixture SelfContainedApplicationFixture { get; } + public string ComponentTypeName { get; } + public string ComponentEntryPoint1 => "ComponentEntryPoint1"; + public string FunctionPointerTypeName { get; } + public string FunctionPointerEntryPoint1 => "FunctionPointerEntryPoint1"; + public string FunctionPointerEntryPoint2 => "FunctionPointerEntryPoint2"; + public string UnmanagedFunctionPointerEntryPoint1 => "UnmanagedFunctionPointerEntryPoint1"; + + public SharedTestState() + { + var dotNet = new Microsoft.DotNet.Cli.Build.DotNetCli(Path.Combine(TestArtifact.TestArtifactsPath, "sharedFrameworkPublish")); + DotNetRoot = dotNet.BinPath; + HostFxrPath = dotNet.GreatestVersionHostFxrFilePath; + + ApplicationFixture = new TestProjectFixture("AppWithCustomEntryPoints", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(selfContained: "false"); + ComponentWithNoDependenciesFixture = new TestProjectFixture("ComponentWithNoDependencies", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(); + SelfContainedApplicationFixture = new TestProjectFixture("AppWithCustomEntryPoints", RepoDirectories) + .EnsureRestored(RepoDirectories.CorehostPackages) + .PublishProject(selfContained: "true"); + ComponentTypeName = $"Component.Component, {ComponentWithNoDependenciesFixture.TestProject.AssemblyName}"; + FunctionPointerTypeName = $"AppWithCustomEntryPoints.Program, {ApplicationFixture.TestProject.AssemblyName}"; + } + + protected override void Dispose(bool disposing) + { + if (ApplicationFixture != null) + ApplicationFixture.Dispose(); + if (ComponentWithNoDependenciesFixture != null) + ComponentWithNoDependenciesFixture.Dispose(); + if (SelfContainedApplicationFixture != null) + SelfContainedApplicationFixture.Dispose(); + + base.Dispose(disposing); + } + } + } + + internal static class FunctionPointerLoadingResultExtensions + { + public static FluentAssertions.AndConstraint ExecuteFunctionPointer(this CommandResultAssertions assertion, string methodName, int functionPointerCallCount, int returnValue) + { + return assertion.ExecuteFunctionPointer(methodName, functionPointerCallCount) + .And.HaveStdOutContaining($"{methodName} delegate result: 0x{returnValue.ToString("x")}"); + } + + public static FluentAssertions.AndConstraint ExecuteFunctionPointerWithException(this CommandResultAssertions assertion, string methodName, int functionPointerCallCount) + { + var constraint = assertion.ExecuteFunctionPointer(methodName, functionPointerCallCount); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return constraint.And.HaveStdOutContaining($"{methodName} delegate threw exception: 0x{Constants.ErrorCode.COMPlusException.ToString("x")}"); + } + else + { + // Exception is unhandled by native host on non-Windows systems + return constraint.And.ExitWith(Constants.ErrorCode.SIGABRT) + .And.HaveStdErrContaining($"Unhandled exception. System.InvalidOperationException: {methodName}"); + } + } + + public static FluentAssertions.AndConstraint ExecuteFunctionPointer(this CommandResultAssertions assertion, string methodName, int functionPointerCallCount) + { + return assertion.HaveStdOutContaining($"Called {methodName}(0xdeadbeef, 42) - function pointer call count: {functionPointerCallCount}"); + } + } +} diff --git a/src/installer/tests/HostActivation.Tests/NativeHosting/ComponentActivation.cs b/src/installer/tests/HostActivation.Tests/NativeHosting/LoadAssemblyAndGetFunctionPointer.cs similarity index 96% rename from src/installer/tests/HostActivation.Tests/NativeHosting/ComponentActivation.cs rename to src/installer/tests/HostActivation.Tests/NativeHosting/LoadAssemblyAndGetFunctionPointer.cs index d9b600550b60e7..784e3b9caf44ea 100644 --- a/src/installer/tests/HostActivation.Tests/NativeHosting/ComponentActivation.cs +++ b/src/installer/tests/HostActivation.Tests/NativeHosting/LoadAssemblyAndGetFunctionPointer.cs @@ -11,14 +11,14 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting { - public partial class ComponentActivation : IClassFixture + public partial class LoadAssemblyAndGetFunctionPointer : IClassFixture { private const string ComponentLoadAssemblyAndGetFunctionPointerArg = "component_load_assembly_and_get_function_pointer"; private const string AppLoadAssemblyAndGetFunctionPointerArg = "app_load_assembly_and_get_function_pointer"; private readonly SharedTestState sharedState; - public ComponentActivation(SharedTestState sharedTestState) + public LoadAssemblyAndGetFunctionPointer(SharedTestState sharedTestState) { sharedState = sharedTestState; } @@ -262,8 +262,12 @@ public SharedTestState() protected override void Dispose(bool disposing) { + if (ApplicationFixture != null) + ApplicationFixture.Dispose(); if (ComponentWithNoDependenciesFixture != null) ComponentWithNoDependenciesFixture.Dispose(); + if (SelfContainedApplicationFixture != null) + SelfContainedApplicationFixture.Dispose(); base.Dispose(disposing); }