-
Notifications
You must be signed in to change notification settings - Fork 96
DotNET: Implement new bootstrap #1756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
479fe13
SWIG: Remove unused plugin code.
jhett12321 29714a1
DotNET: Bootstrap after NWNX loaded. Implement simple function export.
jhett12321 aa3a26f
Update example C# code, documentation, examples.
jhett12321 8878264
Formatting. Don't export CrashHandler.
jhett12321 0332fae
Remove unused config option from deprecation message.
jhett12321 ee86c08
Simplify event handler registration.
jhett12321 ca9a331
Update examples.
jhett12321 1cefa92
Update documentation.
jhett12321 3b90519
Deprecate redundant/unused functions.
jhett12321 9a335e4
Update changelog.
jhett12321 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| add_plugin(DotNET | ||
| add_shared_plugin(DotNET | ||
| "DotNET.cpp" | ||
| "DotNETExports.cpp") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| // https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting | ||
| #include "nwnx.hpp" | ||
| #include "DotNET.hpp" | ||
|
|
||
| #include <algorithm> | ||
| #include <string> | ||
|
|
@@ -15,18 +16,53 @@ using namespace NWNXLib::API; | |
|
|
||
| namespace DotNET { | ||
|
|
||
| std::vector<void*> GetExports(); | ||
| static void DotNET() __attribute__((constructor)); | ||
| extern std::vector<void*> GetExports(); | ||
| extern AllHandlers s_handlers; | ||
|
|
||
| static void* LoadNetHost(); | ||
| static bool LoadHostFxr(void* nethost); | ||
| static void LoadNetCore(const std::string& assembly); | ||
| static void CoreMessageHandler(const std::vector<std::string>& message); | ||
| static void Bootstrap(); | ||
|
|
||
| static hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config; | ||
| static hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate; | ||
| static hostfxr_close_fn hostfxr_close; | ||
| static hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config; | ||
| static hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate; | ||
| static hostfxr_close_fn hostfxr_close; | ||
| static load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer; | ||
|
|
||
| static void DotNET() __attribute__((constructor)); | ||
|
|
||
| static bool InitThunks() | ||
| static void DotNET() | ||
| { | ||
| void *nethost = nullptr; | ||
| void *hostfxr = nullptr; | ||
| void* nethost = LoadNetHost(); | ||
| if (!nethost) | ||
| { | ||
| LOG_ERROR("Unable to load libnethost.so. .NET plugin will be unavailable."); | ||
| LOG_ERROR("If you're not using the .NET plugin, you can disable this message with 'NWNX_DOTNET_SKIP=y'"); | ||
| return; | ||
| } | ||
|
|
||
| if (!LoadHostFxr(nethost)) | ||
| { | ||
| LOG_ERROR("Unable to load hostfxr.so. .NET plugin will be unavailable."); | ||
| return; | ||
| } | ||
|
|
||
| const auto assembly = Config::Get<std::string>("ASSEMBLY", ""); | ||
| if (assembly.empty()) | ||
| { | ||
| LOG_ERROR("NWNX_DOTNET_ASSEMBLY not specified. DotNET plugin will not be loaded."); | ||
| LOG_ERROR("If you're not using the .NET plugin, you can disable this message with 'NWNX_DOTNET_SKIP=y'"); | ||
| return; | ||
| } | ||
|
|
||
| LoadNetCore(assembly); | ||
| MessageBus::Subscribe("NWNX_CORE_SIGNAL", CoreMessageHandler); | ||
| } | ||
|
|
||
| void* LoadNetHost() | ||
| { | ||
| void* nethost = nullptr; | ||
| if (auto nethost_path = Config::Get<std::string>("NETHOST_PATH")) | ||
| { | ||
| nethost = dlopen(nethost_path->c_str(), RTLD_LAZY); | ||
|
|
@@ -40,12 +76,12 @@ static bool InitThunks() | |
| "./libnethost.so", | ||
| "lib/libnethost.so" | ||
| }; | ||
| for (size_t i = 0; i < std::size(paths); i++) | ||
| for (auto& path : paths) | ||
| { | ||
| nethost = dlopen(paths[i], RTLD_LAZY); | ||
| nethost = dlopen(path, RTLD_LAZY); | ||
| if (nethost) | ||
| { | ||
| LOG_INFO("Loaded libnethost.so from: %s (autodetected)", paths[i]); | ||
| LOG_INFO("Loaded libnethost.so from: %s (autodetected)", path); | ||
| break; | ||
| } | ||
| } | ||
|
|
@@ -56,11 +92,10 @@ static bool InitThunks() | |
| const auto hostBaseDir = "/usr/share/dotnet/packs/Microsoft.NETCore.App.Host.linux-x64/"; | ||
| const auto hostLibSuffix = "/runtimes/linux-x64/native/libnethost.so"; | ||
|
|
||
| DIR* dir = opendir(hostBaseDir); | ||
|
|
||
| auto dir = opendir(hostBaseDir); | ||
| if (dir != nullptr) | ||
| { | ||
| dirent* directoryEntry = readdir(dir); | ||
| auto* directoryEntry = readdir(dir); | ||
| std::vector<std::string> paths; | ||
|
|
||
| while (directoryEntry != nullptr) | ||
|
|
@@ -79,10 +114,9 @@ static bool InitThunks() | |
| if (!paths.empty()) | ||
| { | ||
| std::sort(paths.begin(), paths.end(), std::greater<std::string>()); | ||
| for (std::string path : paths) | ||
| for (const std::string& path : paths) | ||
| { | ||
| nethost = dlopen(path.c_str(), RTLD_LAZY); | ||
|
|
||
| if (nethost) | ||
| { | ||
| LOG_INFO("Loaded libnethost.so from: %s (autodetected)", path); | ||
|
|
@@ -93,13 +127,11 @@ static bool InitThunks() | |
| } | ||
| } | ||
|
|
||
| if (!nethost) | ||
| { | ||
| LOG_ERROR("Unable to load libnethost.so. .NET plugin will be unavailable."); | ||
| LOG_ERROR("If you're not using the .NET plugin, you can disable this message with 'NWNX_DOTNET_SKIP=y'"); | ||
| return false; | ||
| } | ||
| return nethost; | ||
| } | ||
|
|
||
| bool LoadHostFxr(void* nethost) | ||
| { | ||
| auto get_hostfxr_path = (int(*)(char*,size_t*,const void*))dlsym(nethost, "get_hostfxr_path"); | ||
| ASSERT_OR_RETURN(get_hostfxr_path != nullptr, false); | ||
|
|
||
|
|
@@ -108,7 +140,7 @@ static bool InitThunks() | |
| ASSERT_OR_RETURN(get_hostfxr_path(buffer, &buffer_size, nullptr) == 0, false); | ||
| dlclose(nethost); | ||
|
|
||
| hostfxr = dlopen(buffer, RTLD_LAZY); | ||
| void *hostfxr = dlopen(buffer, RTLD_LAZY); | ||
| ASSERT_OR_RETURN(hostfxr != nullptr, false); | ||
|
|
||
| hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)dlsym(hostfxr, "hostfxr_initialize_for_runtime_config"); | ||
|
|
@@ -122,50 +154,85 @@ static bool InitThunks() | |
| return true; | ||
| } | ||
|
|
||
| static void DotNET() | ||
| void LoadNetCore(const std::string& assembly) | ||
| { | ||
| // If the initial lib loads failed, we probably don't have .NET installed. | ||
| if (!InitThunks()) | ||
| return; | ||
|
|
||
| auto assembly = Config::Get<std::string>("ASSEMBLY"); | ||
| auto entrypoint = Config::Get<std::string>("ENTRYPOINT", "NWN.Internal"); | ||
|
|
||
| if (!assembly.has_value()) | ||
| { | ||
| LOG_ERROR("NWNX_DOTNET_ASSEMBLY not specified. DotNET plugin will not be loaded."); | ||
| LOG_ERROR("If you're not using the .NET plugin, you can disable this message with 'NWNX_DOTNET_SKIP=y'"); | ||
| return; | ||
| } | ||
|
|
||
| // Load .NET Core | ||
| hostfxr_handle cxt = nullptr; | ||
| auto runtimeconfig = *assembly + ".runtimeconfig.json"; | ||
| int rc = hostfxr_initialize_for_runtime_config(runtimeconfig.c_str(), nullptr, &cxt); | ||
| if (rc != 0 || cxt == nullptr) | ||
| LOG_FATAL("Unable to load runtime config '%s'; rc=0x%x", runtimeconfig, rc); | ||
|
|
||
| auto runtimeConfig = assembly + ".runtimeConfig.json"; | ||
| int returnCode = hostfxr_initialize_for_runtime_config(runtimeConfig.c_str(), nullptr, &cxt); | ||
| if (returnCode != 0 || cxt == nullptr) | ||
| LOG_FATAL("Unable to load runtime config '%s'; returnCode=0x%x", runtimeConfig, returnCode); | ||
|
|
||
| // Get the load assembly function pointer | ||
| load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = nullptr; | ||
| rc = hostfxr_get_runtime_delegate(cxt, hdt_load_assembly_and_get_function_pointer, (void**)&load_assembly_and_get_function_pointer); | ||
| if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) | ||
| LOG_FATAL("Unable to get load_assembly_and_get_function_pointer; rc=0x%x", rc); | ||
| load_assembly_and_get_function_pointer = nullptr; | ||
| returnCode = hostfxr_get_runtime_delegate(cxt, hdt_load_assembly_and_get_function_pointer, (void**)&load_assembly_and_get_function_pointer); | ||
| if (returnCode != 0 || load_assembly_and_get_function_pointer == nullptr) | ||
| LOG_FATAL("Unable to get load_assembly_and_get_function_pointer; returnCode=0x%x", returnCode); | ||
|
|
||
| hostfxr_close(cxt); | ||
| } | ||
|
|
||
| void CoreMessageHandler(const std::vector<std::string> &message) | ||
| { | ||
| const std::string& messageType = message[0]; | ||
|
|
||
| component_entry_point_fn bootstrap = nullptr; | ||
| auto dll = *assembly + ".dll"; | ||
| auto full_ep = entrypoint + ", " + assembly->substr(assembly->find_last_of("/\\") + 1); | ||
| rc = load_assembly_and_get_function_pointer(dll.c_str(), full_ep.c_str(), | ||
| "Bootstrap", nullptr, nullptr, (void**)&bootstrap); | ||
| if (rc != 0 || bootstrap == nullptr) | ||
| LOG_FATAL("Unable to get %s.Bootstrap() function: dll='%s'; rc=0x%x", full_ep, dll, rc); | ||
| if (messageType == "ON_NWNX_LOADED") | ||
| { | ||
| Bootstrap(); | ||
| } | ||
|
Comment on lines
+179
to
+182
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bootstrapping was moved to the end of NWNX init to allow for all enabled NWNX plugins to be loaded, in-case C# code attempts to use exports from other plugins. |
||
| if (s_handlers.Signal) | ||
| { | ||
| if (API::Globals::VirtualMachine()) | ||
| { | ||
| int spBefore = Utils::PushScriptContext(Constants::OBJECT_INVALID, 0, false); | ||
| s_handlers.Signal(message[0].c_str()); | ||
| int spAfter = Utils::PopScriptContext(); | ||
| ASSERT_MSG(spBefore == spAfter, "spBefore=%x, spAfter=%x", spBefore, spAfter); | ||
| } | ||
| else | ||
| { | ||
| s_handlers.Signal(message[0].c_str()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| std::vector<void*> args = GetExports(); | ||
| rc = bootstrap(args.data(), args.size()*sizeof(void*)); | ||
| if (rc != 0) | ||
| LOG_FATAL("Failed to execute bootstrap function; rc=0x%x", rc); | ||
| void Bootstrap() | ||
| { | ||
| const auto assembly = Config::Get<std::string>("ASSEMBLY", ""); | ||
| const auto entrypoint = Config::Get<std::string>("ENTRYPOINT", "NWN.Internal"); | ||
| const auto method = Config::Get<std::string>("METHOD", "Bootstrap"); | ||
| const auto newBootstrap = Config::Get<bool>("NEW_BOOTSTRAP", false); | ||
|
|
||
| int returnCode = 0; | ||
| auto assemblyPath = assembly + ".dll"; | ||
| auto fullTypeName = entrypoint + ", " + assembly.substr(assembly.find_last_of("/\\") + 1); | ||
|
|
||
| if (newBootstrap) | ||
| { | ||
| void (*bootstrap)() = nullptr; | ||
| returnCode = load_assembly_and_get_function_pointer(assemblyPath.c_str(), fullTypeName.c_str(), | ||
| method.c_str(), "System.Action, System.Runtime", nullptr, (void**)&bootstrap); | ||
| if (returnCode != 0 || bootstrap == nullptr) | ||
| LOG_FATAL("Unable to get %s.%s() function: assemblyPath='%s'; returnCode=0x%x", fullTypeName, method, assemblyPath, returnCode); | ||
|
|
||
| bootstrap(); | ||
| if (returnCode != 0) | ||
| LOG_FATAL("Failed to execute bootstrap function; returnCode=0x%x", returnCode); | ||
| } | ||
| else | ||
| { | ||
| LOG_WARNING("Using legacy bootstrap method for function exports. This will be removed in a future release. Set NWNX_DOTNET_NEW_BOOTSTRAP to use the new bootstrap method and hide this message."); | ||
| component_entry_point_fn bootstrap = nullptr; | ||
| returnCode = load_assembly_and_get_function_pointer(assemblyPath.c_str(), fullTypeName.c_str(), | ||
| method.c_str(), nullptr, nullptr, (void**)&bootstrap); | ||
| if (returnCode != 0 || bootstrap == nullptr) | ||
| LOG_FATAL("Unable to get %s.%s() function: assemblyPath='%s'; returnCode=0x%x", fullTypeName, method, assemblyPath, returnCode); | ||
|
|
||
| std::vector<void*> args = GetExports(); | ||
| returnCode = bootstrap(args.data(), args.size() * sizeof(void*)); | ||
| if (returnCode != 0) | ||
| LOG_FATAL("Failed to execute bootstrap function; returnCode=0x%x", returnCode); | ||
| } | ||
|
|
||
| LOG_INFO("Managed code bootstrapped."); | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #pragma once | ||
|
|
||
| namespace DotNET { | ||
| using MainLoopHandler = void (*)(uint64_t); | ||
| using RunScriptHandler = int (*)(const char*, uint32_t); | ||
| using ClosureHandler = void (*)(uint64_t, uint32_t); | ||
| using SignalHandler = void (*)(const char*); | ||
| using AssertHandler = void (*)(const char*, const char*); | ||
| using CrashHandler = void (*)(int, const char*); | ||
|
|
||
| struct AllHandlers | ||
| { | ||
| MainLoopHandler MainLoop; | ||
| RunScriptHandler RunScript; | ||
| ClosureHandler Closure; | ||
| SignalHandler Signal; | ||
| AssertHandler Assert; | ||
| CrashHandler Crash; | ||
| }; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PInvoke cannot accessed exported symbols of "Module" visible libraries, so I added a new function here with different visibility.
We can also change the plugin default to have shared visibility, which would allow C# code to directly call exports from other NWNX plugins.
I can inline this directly in
DotNET/CMakeListsif you do not want this added here.