From ebc21d33989871b4443630c4e0bd5922bbdf2748 Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Thu, 7 Sep 2023 06:09:43 -0700 Subject: [PATCH] Update hosting sample to also show hostfxr_run_app and hdt_get_function_pointer (#6145) --- core/hosting/build.proj | 1 + core/hosting/readme.md | 13 + core/hosting/src/App/App.cs | 32 +++ core/hosting/src/App/App.csproj | 14 ++ core/hosting/src/HostWithHostFxr.sln | 18 +- core/hosting/src/NativeHost/nativehost.cpp | 267 ++++++++++++++------- 6 files changed, 259 insertions(+), 86 deletions(-) create mode 100644 core/hosting/src/App/App.cs create mode 100644 core/hosting/src/App/App.csproj diff --git a/core/hosting/build.proj b/core/hosting/build.proj index e1d2fa1ff13..0474d06eeee 100644 --- a/core/hosting/build.proj +++ b/core/hosting/build.proj @@ -10,6 +10,7 @@ + \ No newline at end of file diff --git a/core/hosting/readme.md b/core/hosting/readme.md index e89d4f43860..38b0249309d 100644 --- a/core/hosting/readme.md +++ b/core/hosting/readme.md @@ -62,6 +62,19 @@ Hello, world! from CustomEntryPointUnmanagedCallersOnly in Lib -- number: -1 ``` +To make this sample run a managed app instead of loading a class library, launch the `nativehost` passing `app` as a command line argument. The expected output will come from the `App` application: + +```console +App started - args = [ app_arg_1, app_arg_2 ] +Hello, world! from App [count: 1] +-- message: from host! +Hello, world! from App [count: 2] +-- message: from host! +Hello, world! from App [count: 3] +-- message: from host! +Signaling app to close +``` + Note: The way the sample is built is relatively complicated. The goal is that it's possible to build and run the sample with simple `dotnet run` with minimal requirements on pre-installed tools. Typically, real-world projects that have both managed and native components will use different build systems for each; for example, msbuild/dotnet for managed and CMake for native. ## Visual Studio support diff --git a/core/hosting/src/App/App.cs b/core/hosting/src/App/App.cs new file mode 100644 index 00000000000..86816047786 --- /dev/null +++ b/core/hosting/src/App/App.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; + +public class App +{ + private static byte isWaiting = 0; + private static int s_CallCount = 0; + private static ManualResetEvent mre = new ManualResetEvent(false); + + public static void Main(string[] args) + { + Console.WriteLine($"{nameof(App)} started - args = [ {string.Join(", ", args)} ]"); + isWaiting = 1; + mre.WaitOne(); + } + + [UnmanagedCallersOnly] + public static byte IsWaiting() => isWaiting; + + [UnmanagedCallersOnly] + public static void Hello(IntPtr message) + { + Console.WriteLine($"Hello, world! from {nameof(App)} [count: {++s_CallCount}]"); + Console.WriteLine($"-- message: {Marshal.PtrToStringUTF8(message)}"); + if (s_CallCount >= 3) + { + Console.WriteLine("Signaling app to close"); + mre.Set(); + } + } +} diff --git a/core/hosting/src/App/App.csproj b/core/hosting/src/App/App.csproj new file mode 100644 index 00000000000..6e0991d7d80 --- /dev/null +++ b/core/hosting/src/App/App.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + false + + + + $(BinRoot)/$(Configuration)/ + false + + + diff --git a/core/hosting/src/HostWithHostFxr.sln b/core/hosting/src/HostWithHostFxr.sln index 739819fadc4..881aa4ed370 100644 --- a/core/hosting/src/HostWithHostFxr.sln +++ b/core/hosting/src/HostWithHostFxr.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29102.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34030.414 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeHost.vs", "NativeHost\NativeHost.vs.vcxproj", "{B99BC289-621D-4DB6-A964-2D869F8E0DD3}" EndProject @@ -11,6 +11,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{91651AB7 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeHost", "NativeHost\NativeHost.csproj", "{0A829E27-48E8-4A1E-BD69-74A2DCFC87C7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "App\App.csproj", "{7E9BD819-E143-4347-9430-68B583BEFDB7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -53,6 +55,18 @@ Global {0A829E27-48E8-4A1E-BD69-74A2DCFC87C7}.Release|x64.Build.0 = Release|Any CPU {0A829E27-48E8-4A1E-BD69-74A2DCFC87C7}.Release|x86.ActiveCfg = Release|Any CPU {0A829E27-48E8-4A1E-BD69-74A2DCFC87C7}.Release|x86.Build.0 = Release|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Debug|x64.ActiveCfg = Debug|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Debug|x64.Build.0 = Debug|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Debug|x86.ActiveCfg = Debug|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Debug|x86.Build.0 = Debug|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Release|Any CPU.Build.0 = Release|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Release|x64.ActiveCfg = Release|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Release|x64.Build.0 = Release|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Release|x86.ActiveCfg = Release|Any CPU + {7E9BD819-E143-4347-9430-68B583BEFDB7}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/core/hosting/src/NativeHost/nativehost.cpp b/core/hosting/src/NativeHost/nativehost.cpp index 7e5d67d3ae3..0fd473025fd 100644 --- a/core/hosting/src/NativeHost/nativehost.cpp +++ b/core/hosting/src/NativeHost/nativehost.cpp @@ -8,7 +8,10 @@ #include #include #include +#include #include +#include +#include // Provided by the AppHost NuGet package and installed as an SDK pack #include @@ -24,6 +27,8 @@ #define CH(c) L ## c #define DIR_SEPARATOR L'\\' +#define string_compare wcscmp + #else #include #include @@ -33,6 +38,8 @@ #define DIR_SEPARATOR '/' #define MAX_PATH PATH_MAX +#define string_compare strcmp + #endif using string_t = std::basic_string; @@ -40,13 +47,18 @@ using string_t = std::basic_string; namespace { // Globals to hold hostfxr exports - hostfxr_initialize_for_runtime_config_fn init_fptr; + hostfxr_initialize_for_dotnet_command_line_fn init_for_cmd_line_fptr; + hostfxr_initialize_for_runtime_config_fn init_for_config_fptr; hostfxr_get_runtime_delegate_fn get_delegate_fptr; + hostfxr_run_app_fn run_app_fptr; hostfxr_close_fn close_fptr; // Forward declarations - bool load_hostfxr(); + bool load_hostfxr(const char_t *app); load_assembly_and_get_function_pointer_fn get_dotnet_load_assembly(const char_t *assembly); + + int run_component_example(const string_t& root_path); + int run_app_example(const string_t& root_path); } #if defined(WINDOWS) @@ -71,97 +83,181 @@ int main(int argc, char *argv[]) assert(pos != string_t::npos); root_path = root_path.substr(0, pos + 1); - // - // STEP 1: Load HostFxr and get exported hosting functions - // - if (!load_hostfxr()) + if (argc > 1 && string_compare(argv[1], STR("app")) == 0) { - assert(false && "Failure: load_hostfxr()"); - return EXIT_FAILURE; + return run_app_example(root_path); } - - // - // STEP 2: Initialize and start the .NET Core runtime - // - const string_t config_path = root_path + STR("DotNetLib.runtimeconfig.json"); - load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr; - load_assembly_and_get_function_pointer = get_dotnet_load_assembly(config_path.c_str()); - assert(load_assembly_and_get_function_pointer != nullptr && "Failure: get_dotnet_load_assembly()"); - - // - // STEP 3: Load managed assembly and get function pointer to a managed method - // - const string_t dotnetlib_path = root_path + STR("DotNetLib.dll"); - const char_t *dotnet_type = STR("DotNetLib.Lib, DotNetLib"); - const char_t *dotnet_type_method = STR("Hello"); - // - // Function pointer to managed delegate - component_entry_point_fn hello = nullptr; - int rc = load_assembly_and_get_function_pointer( - dotnetlib_path.c_str(), - dotnet_type, - dotnet_type_method, - nullptr /*delegate_type_name*/, - nullptr, - (void**)&hello); - // - assert(rc == 0 && hello != nullptr && "Failure: load_assembly_and_get_function_pointer()"); - - // - // STEP 4: Run managed code - // - struct lib_args + else { - const char_t *message; - int number; - }; - for (int i = 0; i < 3; ++i) + return run_component_example(root_path); + } +} + +namespace +{ + int run_component_example(const string_t& root_path) { - // + // + // STEP 1: Load HostFxr and get exported hosting functions + // + if (!load_hostfxr(nullptr)) + { + assert(false && "Failure: load_hostfxr()"); + return EXIT_FAILURE; + } + + // + // STEP 2: Initialize and start the .NET Core runtime + // + const string_t config_path = root_path + STR("DotNetLib.runtimeconfig.json"); + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr; + load_assembly_and_get_function_pointer = get_dotnet_load_assembly(config_path.c_str()); + assert(load_assembly_and_get_function_pointer != nullptr && "Failure: get_dotnet_load_assembly()"); + + // + // STEP 3: Load managed assembly and get function pointer to a managed method + // + const string_t dotnetlib_path = root_path + STR("DotNetLib.dll"); + const char_t *dotnet_type = STR("DotNetLib.Lib, DotNetLib"); + const char_t *dotnet_type_method = STR("Hello"); + // + // Function pointer to managed delegate + component_entry_point_fn hello = nullptr; + int rc = load_assembly_and_get_function_pointer( + dotnetlib_path.c_str(), + dotnet_type, + dotnet_type_method, + nullptr /*delegate_type_name*/, + nullptr, + (void**)&hello); + // + assert(rc == 0 && hello != nullptr && "Failure: load_assembly_and_get_function_pointer()"); + + // + // STEP 4: Run managed code + // + struct lib_args + { + const char_t *message; + int number; + }; + for (int i = 0; i < 3; ++i) + { + // + lib_args args + { + STR("from host!"), + i + }; + + hello(&args, sizeof(args)); + // + } + + // Function pointer to managed delegate with non-default signature + typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); + custom_entry_point_fn custom = nullptr; lib_args args { STR("from host!"), - i + -1 }; - hello(&args, sizeof(args)); - // + // UnmanagedCallersOnly + rc = load_assembly_and_get_function_pointer( + dotnetlib_path.c_str(), + dotnet_type, + STR("CustomEntryPointUnmanagedCallersOnly") /*method_name*/, + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + (void**)&custom); + assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); + custom(args); + + // Custom delegate type + rc = load_assembly_and_get_function_pointer( + dotnetlib_path.c_str(), + dotnet_type, + STR("CustomEntryPoint") /*method_name*/, + STR("DotNetLib.Lib+CustomEntryPointDelegate, DotNetLib") /*delegate_type_name*/, + nullptr, + (void**)&custom); + assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); + custom(args); + + return EXIT_SUCCESS; } - // Function pointer to managed delegate with non-default signature - typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(lib_args args); - custom_entry_point_fn custom = nullptr; - lib_args args + int run_app_example(const string_t& root_path) { - STR("from host!"), - -1 - }; - - // UnmanagedCallersOnly - rc = load_assembly_and_get_function_pointer( - dotnetlib_path.c_str(), - dotnet_type, - STR("CustomEntryPointUnmanagedCallersOnly") /*method_name*/, - UNMANAGEDCALLERSONLY_METHOD, - nullptr, - (void**)&custom); - assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); - custom(args); - - // Custom delegate type - rc = load_assembly_and_get_function_pointer( - dotnetlib_path.c_str(), - dotnet_type, - STR("CustomEntryPoint") /*method_name*/, - STR("DotNetLib.Lib+CustomEntryPointDelegate, DotNetLib") /*delegate_type_name*/, - nullptr, - (void**)&custom); - assert(rc == 0 && custom != nullptr && "Failure: load_assembly_and_get_function_pointer()"); - custom(args); - - return EXIT_SUCCESS; + const string_t app_path = root_path + STR("App.dll"); + + if (!load_hostfxr(app_path.c_str())) + { + assert(false && "Failure: load_hostfxr()"); + return EXIT_FAILURE; + } + + // Load .NET Core + hostfxr_handle cxt = nullptr; + std::vector args { app_path.c_str(), STR("app_arg_1"), STR("app_arg_2") }; + int rc = init_for_cmd_line_fptr(args.size(), args.data(), nullptr, &cxt); + if (rc != 0 || cxt == nullptr) + { + std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl; + close_fptr(cxt); + return EXIT_FAILURE; + } + + // Get the function pointer to get function pointers + get_function_pointer_fn get_function_pointer; + rc = get_delegate_fptr( + cxt, + hdt_get_function_pointer, + (void**)&get_function_pointer); + if (rc != 0 || get_function_pointer == nullptr) + std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl; + + // Function pointer to App.IsWaiting + typedef unsigned char (CORECLR_DELEGATE_CALLTYPE* is_waiting_fn)(); + is_waiting_fn is_waiting; + rc = get_function_pointer( + STR("App, App"), + STR("IsWaiting"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, nullptr, (void**)&is_waiting); + assert(rc == 0 && is_waiting != nullptr && "Failure: get_function_pointer()"); + + // Function pointer to App.Hello + typedef void (CORECLR_DELEGATE_CALLTYPE* hello_fn)(const char*); + hello_fn hello; + rc = get_function_pointer( + STR("App, App"), + STR("Hello"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, nullptr, (void**)&hello); + assert(rc == 0 && hello != nullptr && "Failure: get_function_pointer()"); + + // Invoke the functions in a different thread from the main app + std::thread t([&] + { + while (is_waiting() != 1) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + for (int i = 0; i < 3; ++i) + hello("from host!"); + }); + + // Run the app + run_app_fptr(cxt); + t.join(); + + close_fptr(cxt); + return EXIT_SUCCESS; + } } + /******************************************************************************************** * Function used to load and activate .NET Core ********************************************************************************************/ @@ -202,22 +298,25 @@ namespace // // Using the nethost library, discover the location of hostfxr and get exports - bool load_hostfxr() + bool load_hostfxr(const char_t *assembly_path) { + get_hostfxr_parameters params { sizeof(get_hostfxr_parameters), assembly_path, nullptr }; // Pre-allocate a large buffer for the path to hostfxr char_t buffer[MAX_PATH]; size_t buffer_size = sizeof(buffer) / sizeof(char_t); - int rc = get_hostfxr_path(buffer, &buffer_size, nullptr); + int rc = get_hostfxr_path(buffer, &buffer_size, ¶ms); if (rc != 0) return false; // Load hostfxr and get desired exports void *lib = load_library(buffer); - init_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config"); + init_for_cmd_line_fptr = (hostfxr_initialize_for_dotnet_command_line_fn)get_export(lib, "hostfxr_initialize_for_dotnet_command_line"); + init_for_config_fptr = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config"); get_delegate_fptr = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate"); + run_app_fptr = (hostfxr_run_app_fn)get_export(lib, "hostfxr_run_app"); close_fptr = (hostfxr_close_fn)get_export(lib, "hostfxr_close"); - return (init_fptr && get_delegate_fptr && close_fptr); + return (init_for_config_fptr && get_delegate_fptr && close_fptr); } // @@ -228,7 +327,7 @@ namespace // Load .NET Core void *load_assembly_and_get_function_pointer = nullptr; hostfxr_handle cxt = nullptr; - int rc = init_fptr(config_path, nullptr, &cxt); + int rc = init_for_config_fptr(config_path, nullptr, &cxt); if (rc != 0 || cxt == nullptr) { std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl;