Skip to content

Commit

Permalink
.NET: Unsubscribe callbacks on unload
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Apr 1, 2024
1 parent a2994f5 commit aacc861
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 25 deletions.
84 changes: 84 additions & 0 deletions csharp-api/REFrameworkNET/Callbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
58 changes: 48 additions & 10 deletions csharp-api/REFrameworkNET/Callbacks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<System::Reflection::FieldInfo^>^ s_knownStaticEvents = gcnew System::Collections::Generic::List<System::Reflection::FieldInfo^>();
};
}

public ref class BaseCallback {
public:
delegate void Delegate();
Expand All @@ -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(); \
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
};
};
}
6 changes: 4 additions & 2 deletions csharp-api/REFrameworkNET/PluginManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type^>{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<Object^>{PluginManager::s_api_instance});
method->Invoke(nullptr, nullptr);
}

Callbacks::Impl::UnsubscribeAssembly(assem);
}
}
catch (System::Exception^ e) {
Expand Down
29 changes: 16 additions & 13 deletions csharp-api/test/Test/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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());
}
}
}
};

0 comments on commit aacc861

Please sign in to comment.