Fix crash on exit when C# code was bound to asset unloading event called after C# shutdown

This commit is contained in:
Wojtek Figat
2025-11-18 18:00:02 +01:00
parent 3efb981f00
commit c0dda45c7b
4 changed files with 14 additions and 1 deletions

View File

@@ -21,6 +21,7 @@
MDomain* MRootDomain = nullptr;
MDomain* MActiveDomain = nullptr;
Array<MDomain*, FixedAllocation<4>> MDomains;
bool MCore::Ready = false;
MClass* MCore::TypeCache::Void = nullptr;
MClass* MCore::TypeCache::Object = nullptr;
@@ -301,6 +302,11 @@ bool MProperty::IsStatic() const
return false;
}
void MCore::OnManagedEventAfterShutdown(const char* eventName)
{
LOG(Error, "Found a binding leak on '{}' event used by C# scripting after shutdown. Ensure to unregister scripting events from objects during disposing.", ::String(eventName));
}
MDomain* MCore::GetRootDomain()
{
return MRootDomain;

View File

@@ -57,6 +57,10 @@ public:
static void UnloadScriptingAssemblyLoadContext();
#endif
// Utility for guarding against using C# scripting runtime after shutdown (eg. when asset delegate is not properly disposed).
static bool Ready;
static void OnManagedEventAfterShutdown(const char* eventName);
public:
/// <summary>
/// Utilities for C# object management.

View File

@@ -316,7 +316,8 @@ bool MCore::LoadEngine()
char* buildInfo = CallStaticMethod<char*>(GetStaticMethodPointer(TEXT("GetRuntimeInformation")));
LOG(Info, ".NET runtime version: {0}", ::String(buildInfo));
MCore::GC::FreeMemory(buildInfo);
GC::FreeMemory(buildInfo);
Ready = true;
return false;
}
@@ -327,6 +328,7 @@ void MCore::UnloadEngine()
return;
PROFILE_CPU();
CallStaticMethod<void>(GetStaticMethodPointer(TEXT("Exit")));
Ready = false;
MDomains.ClearDelete();
MRootDomain = nullptr;
ShutdownHostfxr();

View File

@@ -2043,6 +2043,7 @@ namespace Flax.Build.Bindings
contents.Append(')').AppendLine();
contents.Append(" {").AppendLine();
contents.Append(" static MMethod* method = nullptr;").AppendLine();
contents.AppendFormat(" if (!MCore::Ready) {{ MCore::OnManagedEventAfterShutdown(\"{0}.{1}\"); return; }}", classTypeNameManaged, eventInfo.Name).AppendLine();
contents.AppendFormat(" if (!method) {{ method = {1}::TypeInitializer.GetClass()->GetMethod(\"Internal_{0}_Invoke\", {2}); CHECK(method); }}", eventInfo.Name, classTypeNameNative, paramsCount).AppendLine();
contents.Append(" MObject* exception = nullptr;").AppendLine();
if (paramsCount == 0)