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);
}