diff --git a/csharp-api/REFrameworkNET/Callbacks.cpp b/csharp-api/REFrameworkNET/Callbacks.cpp index 7c3e4ebc1..e05b81320 100644 --- a/csharp-api/REFrameworkNET/Callbacks.cpp +++ b/csharp-api/REFrameworkNET/Callbacks.cpp @@ -48,11 +48,95 @@ void Impl::Setup(REFrameworkNET::API^ api) { continue; } + if (auto pre = type->GetField("PreImplementation", wantedFlags); pre != nullptr) { + s_knownStaticEvents->Add(pre); + } + + if (auto post = type->GetField("PostImplementation", wantedFlags); post != nullptr) { + s_knownStaticEvents->Add(post); + } + IntPtr preHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPre); IntPtr postHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPost); nativeApi->param()->functions->on_pre_application_entry(eventNameStr.c_str(), (REFOnPreApplicationEntryCb)preHookPtr.ToPointer()); nativeApi->param()->functions->on_post_application_entry(eventNameStr.c_str(), (REFOnPostApplicationEntryCb)postHookPtr.ToPointer()); } } + +void Impl::UnsubscribeAssembly(System::Reflection::Assembly^ assembly) { + s_unloading = true; + + if (s_knownStaticEvents->Count == 0) { + REFrameworkNET::API::LogError("REFrameworkNET.Callbacks UnsubscribeAssembly: No known static events to unsubscribe from."); + s_unloading = false; + return; + } + + for each (System::Reflection::FieldInfo ^ ei in s_knownStaticEvents) { + while (true) { + auto pre = ei->GetValue(nullptr); + + if (pre == nullptr) { + break; + } + + auto del = (System::Delegate^)pre; + auto invocationList = del->GetInvocationList(); + + bool set = false; + + for each (System::Delegate ^ d in invocationList) { + // Get the assembly that the delegate is from + auto target = d->Method; + auto targetAssembly = target->DeclaringType->Assembly; + + if (targetAssembly == assembly) { + System::Console::WriteLine("REFrameworkNET.Callbacks UnsubscribeAssembly: Removing " + target->Name + " from " + ei->Name); + auto newDel = (BaseCallback::Delegate^)del - (BaseCallback::Delegate^)d; + ei->SetValue(nullptr, newDel); + set = true; + break; + } + } + + if (!set) { + break; + } + } + + while (true) { + auto post = ei->GetValue(nullptr); + + if (post == nullptr) { + break; + } + + auto del = (System::Delegate^)post; + auto invocationList = del->GetInvocationList(); + + bool set = false; + + for each (System::Delegate ^ d in invocationList) { + // Get the assembly that the delegate is from + auto target = d->Method; + auto targetAssembly = target->DeclaringType->Assembly; + + if (targetAssembly == assembly) { + System::Console::WriteLine("REFrameworkNET.Callbacks UnsubscribeAssembly: Removing " + target->Name + " from " + ei->Name); + auto newDel = (BaseCallback::Delegate^)del - (BaseCallback::Delegate^)d; + ei->SetValue(nullptr, newDel); + set = true; + break; + } + } + + if (!set) { + break; + } + } + } + + s_unloading = false; +} } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index 01a8fd0c5..1ff5aeb9b 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -8,6 +8,27 @@ using namespace System::Collections::Generic; namespace REFrameworkNET { ref class API; +namespace Callbacks { + public ref class Impl { + public: + property bool IsUnloading { + static bool get() { + return s_unloading; + } + } + + internal: + static void Setup(REFrameworkNET::API^ api); + static void UnsubscribeAssembly(System::Reflection::Assembly^ assembly); + + private: + static bool s_setup{false}; + static bool s_unloading{false}; + + static System::Collections::Generic::List^ s_knownStaticEvents = gcnew System::Collections::Generic::List(); + }; +} + public ref class BaseCallback { public: delegate void Delegate(); @@ -16,8 +37,31 @@ public ref class BaseCallback { #define GENERATE_POCKET_CLASS(EVENT_NAME) \ public ref class EVENT_NAME { \ public: \ - static event BaseCallback::Delegate^ Pre; \ - static event BaseCallback::Delegate^ Post; \ + static event BaseCallback::Delegate^ Pre { \ + void add(BaseCallback::Delegate^ value) { \ + PreImplementation += value; \ + } \ + void remove(BaseCallback::Delegate^ value) { \ + PreImplementation -= value; \ + } \ + void raise() { \ + PreImplementation(); \ + } \ + } \ + static event BaseCallback::Delegate^ Post { \ + void add(BaseCallback::Delegate^ value) { \ + PostImplementation += value; \ + } \ + void remove(BaseCallback::Delegate^ value) { \ + PostImplementation -= value; \ + } \ + void raise() { \ + if (Callbacks::Impl::IsUnloading) { \ + return; \ + } \ + PostImplementation(); \ + } \ + } \ internal: \ static void TriggerPre() { \ Pre(); \ @@ -27,6 +71,8 @@ internal: \ } \ static BaseCallback::Delegate^ TriggerPreDelegate = gcnew BaseCallback::Delegate(&EVENT_NAME::TriggerPre); \ static BaseCallback::Delegate^ TriggerPostDelegate = gcnew BaseCallback::Delegate(&EVENT_NAME::TriggerPost); \ + static BaseCallback::Delegate^ PreImplementation; \ + static BaseCallback::Delegate^ PostImplementation; \ }; \ namespace Callbacks { @@ -406,13 +452,5 @@ namespace Callbacks { GENERATE_POCKET_CLASS(FinalizeDialog) GENERATE_POCKET_CLASS(FinalizeMixer) GENERATE_POCKET_CLASS(FinalizeGameCore); - - public ref class Impl { - public: - static void Setup(REFrameworkNET::API^ api); - - private: - static bool s_setup = false; - }; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 2d5b40230..1c0b3df65 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -408,12 +408,14 @@ namespace REFrameworkNET { try { // Look for the Unload method in the target assembly which takes an REFrameworkNET.API instance for each (Type ^ t in assem->GetTypes()) { - auto method = t->GetMethod("OnUnload", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, nullptr, gcnew array{REFrameworkNET::API::typeid}, nullptr); + auto method = t->GetMethod("OnUnload", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); if (method != nullptr) { REFrameworkNET::API::LogInfo("Unloading dynamic assembly by calling " + method->Name + " in " + t->FullName); - method->Invoke(nullptr, gcnew array{PluginManager::s_api_instance}); + method->Invoke(nullptr, nullptr); } + + Callbacks::Impl::UnsubscribeAssembly(assem); } } catch (System::Exception^ e) { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index ef18295f4..2cd8ba80e 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -108,7 +108,7 @@ class REFrameworkPlugin { static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); // To be called when the AssemblyLoadContext is unloading the assembly - public static void OnUnload(REFrameworkNET.API api) { + public static void OnUnload() { REFrameworkNET.API.LogInfo("Unloading Test"); } @@ -152,8 +152,22 @@ public static void TestCallbacks() { } [REFrameworkNET.Attributes.PluginEntryPoint] - public static void Main() { + public static void Main() { try { + MainImpl(); + } catch (Exception e) { + REFrameworkNET.API.LogError(e.ToString()); + + var ex = e; + + while (ex.InnerException != null) { + ex = ex.InnerException; + REFrameworkNET.API.LogError(ex.ToString()); + } + } + } + + public static void MainImpl() { REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); TestCallbacks(); @@ -273,16 +287,5 @@ public static void Main() { foreach (dynamic assembly in assemblies) { REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); } - - } catch (Exception e) { - REFrameworkNET.API.LogError(e.ToString()); - - var ex = e; - - while (ex.InnerException != null) { - ex = ex.InnerException; - REFrameworkNET.API.LogError(ex.ToString()); - } - } } }; \ No newline at end of file