5.5.4 - 5.7.X + Qt for Editor widgets/plugin - REPLACEMENT_OPERATOR_NEW_AND_DELETE

Hello

So here is a fun little problem.

I have TestPluginA and TestPluginB

I get crash either inside PluginA or PLuginB, PluginA usually calls PluginB to do some work. - both PluginA/B are build-loaded via unreal startup routine, HOWEVER, I would eventually want PluginB to only be loaded via OS api as dll, so that I can dynamically load it as needed not as startup.

I’m not passing any objects/etc between the plugins, only string in form of char*.

The crash happens at random, sometimes passing someFunc(“HelloWorld”)

where void someFunc(const QString& data) is signature, or if I pass it via someFunc(QString(“lala”))) etc etc

I’ve spend days on this now, tried numerous flags and every “pro” AI model I could find to help me with it.

Unreal swaps it allocators, and then everything after has a chance of crash.

Sometimes I get crash on 1st call, other on 50th call, Russian roulette.

Can any1 help me out with this ?

REPLACEMENT_OPERATOR_NEW_AND_DELETE

// Copyright Epic Games, Inc. All Rights Reserved.

using UnrealBuildTool;
using System.IO;

public class TestPluginA : ModuleRules
{
public TestPluginA(ReadOnlyTargetRules Target) : base(Target)
{
bUseRTTI = true;
bEnableExceptions = true;
bUseUnity = false;
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

	// CRITICAL: Force ANSI allocator for this module to avoid heap corruption.
	// Qt allocates memory inside Qt DLLs using their own heap. When Qt objects are
	// destroyed in our module code, UE's replaced operator delete tries to free via
	// FMemory::Free(), which crashes because the pointer wasn't allocated by UE.
	// FORCE_ANSI_ALLOCATOR=1 does TWO things:
	// 1. Makes FMemory use standard malloc/free instead of UE's custom allocator
	// 2. Disables REPLACEMENT_OPERATOR_NEW_AND_DELETE (see ModuleBoilerplate.h line 49)
	// This makes the module compatible with external libraries like Qt that use their own heap.
	//PrivateDefinitions.Add("FORCE_ANSI_ALLOCATOR=1");
	//PublicDefinitions.Add("FORCE_ANSI_ALLOCATOR=1");

	// Prefer an environment variable override so builds don’t depend on a machine-local path.
	// Falls back to your current local install.
	string QtPath = System.Environment.GetEnvironmentVariable("QT_DIR");
	if (string.IsNullOrEmpty(QtPath))
	{
		QtPath = System.IO.Path.Combine("C:\\Qt\\6.10.1\\msvc2022_64");
	}
	QtPath = System.IO.Path.GetFullPath(QtPath);
	string MocPath = System.IO.Path.Combine(QtPath, "bin", "moc.exe");
	string RccPath = System.IO.Path.Combine(QtPath, "bin", "rcc.exe");
	string UicPath = System.IO.Path.Combine(QtPath, "bin", "uic.exe");

	string MocDirectory = System.IO.Path.Combine(ModuleDirectory, "Mocs");
	if (!System.IO.Directory.Exists(MocDirectory))
	{
		System.IO.Directory.CreateDirectory(MocDirectory);
	}

	string QrcDirectory = System.IO.Path.Combine(ModuleDirectory, "qrc");
	if (!System.IO.Directory.Exists(QrcDirectory))
	{
		System.IO.Directory.CreateDirectory(QrcDirectory);
	}

	string UiDirectory = System.IO.Path.Combine(ModuleDirectory, "UI");
	if (!System.IO.Directory.Exists(UiDirectory))
	{
		System.IO.Directory.CreateDirectory(UiDirectory);
	}

	// Auto-build UI files
	BuildUiFiles(UicPath, UiDirectory);
	BuildMocFiles(MocPath, MocDirectory);
	BuildRccFiles(RccPath, QrcDirectory);

	PublicIncludePaths.AddRange(
		new string[]
		{
			// ... add public include paths required here ...
			System.IO.Path.Combine(QtPath, "include"),
			System.IO.Path.Combine(QtPath, "include\\QtCore"),
			System.IO.Path.Combine(QtPath, "include\\QtGui"),
			System.IO.Path.Combine(QtPath, "include\\QtWidgets"),
			System.IO.Path.Combine(QtPath, "include\\QtNetwork"),
			"S:\\libsExtra\\json\\active"
		}
	);

	if (Target.Platform == UnrealTargetPlatform.Win64)
	{
		string QtBinPath = System.IO.Path.Combine(QtPath, "bin");
		string QtLibPath = System.IO.Path.Combine(QtPath, "lib");

		// Always use Release Qt - Editor uses Release CRT
		bool useDebugQt =
			Target.Configuration == UnrealTargetConfiguration.Debug ||
			Target.Configuration == UnrealTargetConfiguration.DebugGame;

		string qtCoreLib = useDebugQt ? "Qt6Cored.lib" : "Qt6Core.lib";
		string qtGuiLib  = useDebugQt ? "Qt6Guid.lib"  : "Qt6Gui.lib";
		string qtWLib    = useDebugQt ? "Qt6Widgetsd.lib" : "Qt6Widgets.lib";
		string qtNetLib  = useDebugQt ? "Qt6Networkd.lib" : "Qt6Network.lib";

		PublicAdditionalLibraries.Add(System.IO.Path.Combine(QtLibPath, qtCoreLib));
		PublicAdditionalLibraries.Add(System.IO.Path.Combine(QtLibPath, qtGuiLib));
		PublicAdditionalLibraries.Add(System.IO.Path.Combine(QtLibPath, qtWLib));
		PublicAdditionalLibraries.Add(System.IO.Path.Combine(QtLibPath, qtNetLib));

		// Ensure we’re dynamically linking Qt (import libs + DLLs), not static.
		// NOTE: Qt cannot generally be delay-loaded on Windows because it imports data symbols
		// (e.g. QObject::staticMetaObject), which triggers LNK1194. So we do NOT use /DELAYLOAD.

		// Still stage the DLLs so the Editor/packaged build can load them normally at process start.
		RuntimeDependencies.Add(System.IO.Path.Combine(QtBinPath, useDebugQt ? "Qt6Cored.dll" : "Qt6Core.dll"));
		RuntimeDependencies.Add(System.IO.Path.Combine(QtBinPath, useDebugQt ? "Qt6Guid.dll" : "Qt6Gui.dll"));
		RuntimeDependencies.Add(System.IO.Path.Combine(QtBinPath, useDebugQt ? "Qt6Widgetsd.dll" : "Qt6Widgets.dll"));
		RuntimeDependencies.Add(System.IO.Path.Combine(QtBinPath, useDebugQt ? "Qt6Networkd.dll" : "Qt6Network.dll"));

		//PublicDelayLoadDLLs.Add(useDebugQt ? "Qt6Cored.dll" : "Qt6Core.dll");
		//PublicDelayLoadDLLs.Add(useDebugQt ? "Qt6Guid.dll" : "Qt6Gui.dll");
		//PublicDelayLoadDLLs.Add(useDebugQt ? "Qt6Widgetsd.dll" : "Qt6Widgets.dll");
		//PublicDelayLoadDLLs.Add(useDebugQt ? "Qt6Networkd.dll" : "Qt6Network.dll");

		
		// If you use Qt Widgets, you almost always need the platform plugin at runtime.
		// This stages it so you’re not relying on QT_PLUGIN_PATH being set globally.
		RuntimeDependencies.Add(System.IO.Path.Combine(QtPath, "plugins", "platforms", "qwindows.dll"));

		PublicDefinitions.Add("IC_INTERNAL_BUILD=1");
		PublicDefinitions.Add("QT_NO_KEYWORDS");   // avoids 'signals', 'slots' macro collisions
		//PublicDefinitions.Add("NOMINMAX");         // avoids Windows min/max macro issues
		PublicDefinitions.Add("QT_NO_EXCEPTIONS=0");

		// Reduce growth of macro conflicts when including Qt headers alongside UE headers.
		// (Main fix is to keep Qt includes behind your icQtBegin/icQtEnd wrappers.)
		//PublicDefinitions.Add("WIN32_LEAN_AND_MEAN");
		PublicDefinitions.Add("_CRT_SECURE_NO_WARNINGS");
	}


	PrivateIncludePaths.AddRange(
		new string[]
		{
			// ... add other private include paths required here ...
			MocDirectory,
			QrcDirectory,
			UiDirectory,
		}
	);


	PublicDependencyModuleNames.AddRange(
		new string[]
		{
			"Core",
			"Projects",
			"InputCore",
			"EditorFramework",
			"UnrealEd",
			"LevelEditor",
			"CoreUObject",
			"Engine",
		}
	);


	PrivateDependencyModuleNames.AddRange(
		new string[]
		{
			"Slate",
			"SlateCore",
			"ApplicationCore",
			"PythonScriptPlugin",
			"ToolMenus", "LevelSequenceEditor", "LevelSequence",
			// ... add private dependencies that you statically link with here ...	
		}
	);


	DynamicallyLoadedModuleNames.AddRange(
		new string[]
		{
			// ... add any modules that your module loads dynamically here ...
		}
	);
}

private void BuildMocFiles(string MocPath, string MocDirectory)
{
	if (!System.IO.File.Exists(MocPath))
	{
		System.Console.WriteLine("MOC not found at: " + MocPath);
		return;
	}

	// Search for headers that might contain Q_OBJECT
	string SourcePath = ModuleDirectory;
	string[] HeaderFiles = System.IO.Directory.GetFiles(SourcePath, "*.h", System.IO.SearchOption.AllDirectories);

	foreach (string HeaderFile in HeaderFiles)
	{
		string Content = System.IO.File.ReadAllText(HeaderFile);
		if (Content.Contains("Q_OBJECT"))
		{
			string FileName = System.IO.Path.GetFileNameWithoutExtension(HeaderFile);
			string GeneratedMoc = System.IO.Path.Combine(MocDirectory, "moc_" + FileName + ".cpp");

			if (!System.IO.File.Exists(GeneratedMoc) || System.IO.File.GetLastWriteTime(HeaderFile) > System.IO.File.GetLastWriteTime(GeneratedMoc))
			{
				System.Console.WriteLine("Generating MOC for " + HeaderFile + " -> " + GeneratedMoc);
				System.Diagnostics.ProcessStartInfo StartInfo = new System.Diagnostics.ProcessStartInfo
				{
					FileName = MocPath,
					Arguments = string.Format("\"{0}\" -o \"{1}\"", HeaderFile, GeneratedMoc),
					UseShellExecute = false,
					CreateNoWindow = true,
					RedirectStandardOutput = true,
					RedirectStandardError = true
				};

				using (System.Diagnostics.Process Proc = System.Diagnostics.Process.Start(StartInfo))
				{
					Proc.WaitForExit();
				}
			}
		}
	}
}

private void BuildUiFiles(string UicPath, string UiDirectory)
{
	if (!System.IO.File.Exists(UicPath))
	{
		System.Console.WriteLine("UIC not found at: " + UicPath);
		return;
	}

	string SourcePath = System.IO.Path.Combine(ModuleDirectory, "Shared");
	if (!System.IO.Directory.Exists(SourcePath))
	{
		System.Console.WriteLine("Shared directory not found in: " + ModuleDirectory);
		return;
	}

	string[] UiFiles = System.IO.Directory.GetFiles(SourcePath, "*.ui", System.IO.SearchOption.AllDirectories);
	foreach (string UiFile in UiFiles)
	{
		string FileName = System.IO.Path.GetFileNameWithoutExtension(UiFile);
		string GeneratedHeader = System.IO.Path.Combine(UiDirectory, "ui_" + FileName + ".h");

		if (!System.IO.File.Exists(GeneratedHeader) || System.IO.File.GetLastWriteTime(UiFile) > System.IO.File.GetLastWriteTime(GeneratedHeader))
		{
			System.Console.WriteLine("Generating header for " + UiFile + " -> " + GeneratedHeader);
			System.Diagnostics.ProcessStartInfo StartInfo = new System.Diagnostics.ProcessStartInfo
			{
				FileName = UicPath,
				Arguments = string.Format("\"{0}\" -o \"{1}\"", UiFile, GeneratedHeader),
				UseShellExecute = false,
				CreateNoWindow = true,
				RedirectStandardOutput = true,
				RedirectStandardError = true
			};

			using (System.Diagnostics.Process Proc = System.Diagnostics.Process.Start(StartInfo))
			{
				Proc.WaitForExit();
				if (Proc.ExitCode != 0)
				{
					System.Console.WriteLine("Error generating header for " + UiFile + ": " + Proc.StandardError.ReadToEnd());
				}
			}
		}
	}
}

private void BuildRccFiles(string RccPath, string QrcDirectory)
{
	if (!System.IO.File.Exists(RccPath))
	{
		System.Console.WriteLine("RCC not found at: " + RccPath);
		return;
	}

	string SourcePath = ModuleDirectory;
	string[] RccFiles = System.IO.Directory.GetFiles(SourcePath, "*.qrc", System.IO.SearchOption.AllDirectories);

	foreach (string RccFile in RccFiles)
	{
		string FileName = System.IO.Path.GetFileNameWithoutExtension(RccFile);
		string GeneratedRcc = System.IO.Path.Combine(QrcDirectory, "qrc_" + FileName + ".cpp");

		if (!System.IO.File.Exists(GeneratedRcc) || System.IO.File.GetLastWriteTime(RccFile) > System.IO.File.GetLastWriteTime(GeneratedRcc))
		{
			System.Console.WriteLine("Generating RCC for " + RccFile + " -> " + GeneratedRcc);
			System.Diagnostics.ProcessStartInfo StartInfo = new System.Diagnostics.ProcessStartInfo
			{
				FileName = RccPath,
				Arguments = string.Format("-name \"{0}\" \"{1}\" -o \"{2}\"", FileName, RccFile, GeneratedRcc),
				UseShellExecute = false,
				CreateNoWindow = true,
				RedirectStandardOutput = true,
				RedirectStandardError = true
			};

			using (System.Diagnostics.Process Proc = System.Diagnostics.Process.Start(StartInfo))
			{
				Proc.WaitForExit();
			}
		}
	}
}

}