Win64 Package encounter's fatal error on start due to missing third party dll otherwise present in PIE

DLL not found/linked(?) when packaging for Windows

I have this fatal error below I’m running into that does not appear during PIE sessions. My editor is also compiled from source and rebuilds/grabs the dll’s the sdk plugin we’re developing uses. This error appears on .exe launch. My build target is Windows (Win64).

Note: I have replaced project details with curly brackets, vague names, and matched the casing of the original.

The Fatal Error:

Fatal error!

Unhandled Exception: 0xc06d007e

0x00007fff4a81fe4c KERNELBASE.dll!UnknownFunction []
0x00007ff69bdd1614 {Project_Name}-Win64-DebugGame.exe!__delayLoadHelper2() [D:\a\_work\1\s\src\vctools\delayimp\delayhlp.cpp:312]
0x00007ff69bbab828 {Project_Name}-Win64-DebugGame.exe!_tailMerge_{core_dll}_dll() []
0x00007ff698975289 {Project_Name}-Win64-DebugGame.exe!UPanelContainer::UPanelContainer() [C:\path\to\project\{Project_Name}\Plugins\{Company}\{SDK_Plugin}\Source\Platform\UI\Private\Framework\PanelContainer.cpp:457]
0x00007ff698951bf7 {Project_Name}-Win64-DebugGame.exe!InternalConstructor<UPanelContainer>() [C:\Program Files\Epic Games\UE_5.4\Engine\Source\Runtime\CoreUObject\Public\UObject\Class.h:3559]
0x00007ff68fa039fe {Project_Name}-Win64-DebugGame.exe!UClass::CreateDefaultObject() []
0x00007ff68fdafefb {Project_Name}-Win64-DebugGame.exe!UObjectInitialized() []
0x00007ff68fd93312 {Project_Name}-Win64-DebugGame.exe!ProcessNewlyLoadedUObjects() []
0x00007ff696a3a607 {Project_Name}-Win64-DebugGame.exe!FEngineLoop::PreInitPostStartupScreen() []
0x00007ff696a343d8 {Project_Name}-Win64-DebugGame.exe!GuardedMain() []
0x00007ff696a3465a {Project_Name}-Win64-DebugGame.exe!GuardedMainWrapper() []
0x00007ff696a37225 {Project_Name}-Win64-DebugGame.exe!LaunchWindowsStartup() []
0x00007ff696a47504 {Project_Name}-Win64-DebugGame.exe!WinMain() []
0x00007ff69bdd53ba {Project_Name}-Win64-DebugGame.exe!__scrt_common_main_seh() [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
0x00007fff4c00257d KERNEL32.DLL!UnknownFunction []

My understanding of this is that on the first runtime usage of the {projectcore}.dll done by UPanelContainer’s constructor crashes out because the link is broken.

The Problem

I thought I’d setup the project appropriately to package for windows. But somehow my dll’s linking is broken.

From what I’ve understood reading around the forums and documenation, the pattern for including a ThirdParty (non-windows standard dll location like where vcpkg installs to) dll’s is to do the following:

Within the ThirdParty plugin module {ProjectCoreLibrary}:

  1. PublicDelayLoadDLLs.Add("{projectcore}.dll");
  2. RuntimeDependencies.Add(Path.Combine(binFolder, "{projectcore}.dll"));
    3. Cannot do RuntimeDependencies.Add(string inPath, string inSourcePath); because editor and project is compiled (from source?) via VS. Packaging for Windows is only possible through editor and this setup causes the compiled dll’s to be locked by the editor.

Then within the plugin module:

  1. `PublicDependencyModuleNames.AddRange( new string {“{ProjectCoreLibrary}”});
  2. Inside {ProjectCoreLibrary}.cpp
    1. FPlatformProcess::GetDllHandle(*LibraryPath)
    2. LibraryPath pointing to the Plugin\...\{ProjectCore}\Binaries\Win64 which contains the dll’s.

Relevant code

The plugins within my project uses a thirdparty {projectcore}.dll, this core project sits outside the unreal project and we use the UE4CMake (with minor tune ups for UE5 paths) to build this external dll and drop it into the unreal project.

ThirdParty/{ProjectCoreLibrary}/{ProjectCoreLibrary}.Build.cs

Contains:

public class ProjectCoreLibrary : ModuleRules
{
	public ProjectCoreLibrary(ReadOnlyTargetRules Target) : base(Target)
	{
		Type = ModuleType.External;

		var projectCoreSdkDevFolder = System.Environment.GetEnvironmentVariable("PROJECT_CORE_SDK");

		if (Target.Platform == UnrealTargetPlatform.Win64 && !string.IsNullOrEmpty(projectCoreSdkDevFolder))
        {
	        // Uses the UE4CMAKE project plugin to build the core dll and adds it as a dependency into this ThirdParty project
			CMakeTarget.add(Target, this, "projectcore", projectCoreSdkDevFolder, " --trace");
		}
		

		bool isDebug = Target.Configuration == UnrealTargetConfiguration.Debug ||
			Target.Configuration == UnrealTargetConfiguration.DebugGame;

		var targetBuildConfig = isDebug ? "Debug" : "Release";

		if (Target.Platform == UnrealTargetPlatform.Win64)
		{

			var binFolder = !string.IsNullOrEmpty(projectCoreSdkDevFolder) ?
				Path.Combine(PluginDirectory, "..", "..", "..", "Intermediate", "CMakeTarget", "{projectcore}", "build", "{projectcore}", "{Project.Core}", targetBuildConfig) :
				Path.Combine(PluginDirectory, "Redist", "Win64");
			//  binFolder = C:/Path\To\Project\Build\Windows\{project_name}\Intermediate\CMakeTarget\{projectcore}\build\{projectcore}\{Project.Core}}\Debug
			
			// Delay-load the DLL, so we can load it from the right place first
            PublicDelayLoadDLLs.Add("{projectcore}.dll");
	        
            RuntimeDependencies.Add(Path.Combine(binFolder, "{projectcore}.dll"));

			// Initially used this and works when compiling the editor and then PIE. 
			// However I cannot package to windows from the compiled editor because the editor has locked the files used that this method wants to overwrite.

			// PluginDirectory = C:/Path/To/{project_name}/Plugins/{Company}/{ProjectCore}/
			// RuntimeDependencies.Add(
            //	Path.Combine(PluginDirectory, "Binaries", "Win64", "{projectcore}.dll"),
            //	Path.Combine(binFolder, "{projectcore}.dll"));


        }
		else
		{
			throw new System.NotSupportedException();
		}
	}
}

{ProjectCore}/{ProjectCore}.Build.cs

Contains:

public class ProjectCore : ModuleRules
{
	public ProjectCore(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				"{ProjectCore}Library",
				"Projects"
				// ... add other public dependencies that you statically link with here ...
			}
		);
	}
}

{ProjectCore}/public/{ProjectCore}.cpp

Contains:


#define LOCTEXT_NAMESPACE "FProjectCoreModule"

void FProjectCoreModule::StartupModule()
{
	std::shared_ptr<int> p;

	UE_LOG(LogTemp, Display, TEXT("{ProjectCore} module started"));
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

	// Get the base directory of this plugin
	FString BaseDir = IPluginManager::Get().FindPlugin("{ProjectCore}")->GetBaseDir();

	FString FullPath = FPaths::ConvertRelativePathToFull(BaseDir);

	UE_LOG(LogTemp, Display, TEXT("{ProjectCore} using base dir=%s"), *BaseDir);
	UE_LOG(LogTemp, Display, TEXT("{ProjectCore} using full path=%s"), *FullPath);

	// Add on the relative location of the third party dll and load it
	FString LibraryPath;
#if PLATFORM_WINDOWS
	LibraryPath = FPaths::Combine(*BaseDir, TEXT("Binaries/Win64/{projectcore}.dll"));

	// Tried using same path that the ThirdParty {ProjectCoreLibrary} has for it's binFolder
	//LibraryPath = FPaths::Combine(
	//	FPaths::ProjectIntermediateDir(), 
	//	TEXT("CMakeTarget/{projectcore}/build/{projectcore}/{Project.Core}/"), 
	//	TEXT("Debug"),
	//	TEXT("{projectcore}.dll"));
#endif // PLATFORM_WINDOWS

	ProjectCoreLibraryHandle = !LibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPath) : nullptr;

	if (ProjectCoreLibraryHandle)
	{
		UE_LOG(LogTemp, Display, TEXT("{ProjectCore} loaded library %s"), *LibraryPath);

		SetupMemory();
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("{ProjectCore} failed to load library %s"), *LibraryPath);
	}
}

void FProjectCoreModule::ShutdownModule()
{
	// Free the dll handle
	if (ProjectCoreLibraryHandle)
	{
		FPlatformProcess::FreeDllHandle(ProjectCoreLibraryHandle);
		ProjectCoreLibraryHandle = nullptr;
	}
}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FProjectCoreModule, ProjectCore)

Solution!

I’ve resolved the fatal error on .exe startup! The problem came down to the dll’s needed to be beside the .exe (also running the .exe generated at {project_name}\Binaries\Win64, not the other one) and so within {ProjectCoreLibrary}.Build.cs I needed to change the section:

PublicDelayLoadDLLs.Add("{projectcore}.dll");
RuntimeDependencies.Add(Path.Combine(binFolder, "{projectcore}.dll")); // My third party DLL

to

PublicDelayLoadDLLs.Add("{projectcore}.dll");
RuntimeDependencies.Add(
    Path.Combine("$(BinaryOutputDir)", "{projectcore}.dll"),
    Path.Combine(binFolder, "{projectcore}.dll")
);

And including a RuntimeDependencies.Add(destination, source); for all the dll’s beside my {projectcore}.dll from the thirdparty build.

I initially had file lock issues that prevented the RuntimeDependencies.Add(destination, source); from packaging but those decided to arbitrarily disappear and I could package Win64 lol.

I can’t say with this in mind I understand how the PIE even works, somehow the editor just knows where the DLL’s are more loosely. While the packaged versions need to be more explicit.

Sources that helped

What helped was re-reading this blog post on third party dll’s.

And finding this forum post on the similar issue.

Hope these help anyone else!