Concept
There are two main types of libraries: Static and Dynamic (also called Shared).
- A static library is statically linked to a program and is available at compile time.
- A dynamic (or shared) library, on the other hand, is dynamically linked and available at runtime.
Static linking assumes that the library code is built into the final block of code, unlike dynamic linking. But the process of dynamic linking takes some time.
It follows that the static library is faster, but the dynamic library takes up less memory.
Dynamic library can also contain an import library which is intended to be statically linked to resolve external references to exported dynamic library functions.
Unreal Engine has built-in tool called UBT (UnrealBuildTool) to manage the build process. All build related code must be in the appropriate build.cs
file of your module (in your project or plugin source code).
Library extensions and prefixes
To briefly understand what your library suits for, you can use the following table.
Platform | Static library | Dynamic library | Library prefix |
---|---|---|---|
Windows | .lib | .dll | n/a |
UNIX-like (Linux, Android, iOS) | .a | .so | lib |
OS X (Mac) | .a | .dylib | lib |
Also keep in mind that the library depends on your architecture. For example, a library built for x32 architecture will not be compatible with x64 architecture
Before the implementation
The examples below use a plugin named PLUGIN_EXAMPLE
and a third party library named lib_[PLATFORM_NAME]_example
(where [PLATFORM_NAME]
is the name of the platform, e.g. win64
, linux
, ios
, etc) located in the PLUGIN_EXAMPLE/Source/ThirdParty/
directory.
How to integrate static library
Let’s say you have a Windows x64 static library named lib_win64_example.lib
.
To integrate this library, you need to add its path to PublicAdditionalLibraries
in your build.cs
:
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "lib_win64_example.lib"));
Click to see full example
using System;
using System.IO;
using UnrealBuildTool;
public class PLUGIN_EXAMPLE : ModuleRules
{
public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject"
}
);
// Include the library
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "lib_win64_example.lib"));
}
}
You then need to include the header files in the file you want to call the library functions from. To remove the various warnings/errors that might cause the build to fail, you can wrap your include with the THIRD_PARTY_INCLUDES_START
and THIRD_PARTY_INCLUDES_END
macros:
THIRD_PARTY_INCLUDES_START
#include "ThirdParty/header_to_include.h"
THIRD_PARTY_INCLUDES_END
How to integrate dynamic library
If the dynamic library comes with import library
If you have an import library, then you do not need to separately import the required functions from the dynamic library.
Let’s say you have a Windows x64 dynamic library named lib_win64_example.dll
and a corresponding import library named lib_win64_import_example.lib
.
To include your import library, you need to add its path to PublicAdditionalLibraries
in your build.cs
:
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "lib_win64_import_example.lib"));
You then need to add your dynamic library as a runtime dependency to put it alongside the executable:
RuntimeDependencies.Add("$(PluginDir)/ThirdParty/lib_win64_example.dll");
Afterwards, on Windows, you will likely need to specify the DLL to be delay-loaded. This is done by adding the following line to your build.cs
:
PublicDelayLoadDLLs.Add("lib_win64_example.dll");
Click to see full example
using System;
using System.IO;
using UnrealBuildTool;
public class PLUGIN_EXAMPLE : ModuleRules
{
public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Projects"
}
);
// Include the import library
PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "..", "ThirdParty", "lib_win64_import_example.lib"));
// Put the library along with the executable
RuntimeDependencies.Add("$(PluginDir)/ThirdParty/lib_win64_example.dll");
// Load library
PublicDelayLoadDLLs.Add("lib_win64_example.dll");
}
}
The next step is to specify when to load and free your dynamic library. In the following example, the lifetime of the library will match the lifetime of the module. That is, the library will be loaded on module startup and freed on module shutdown.
You need to open your module’s interface file PLUGIN_EXAMPLE.h
and add a field that will contain your dynamic library handler:
void* DynamicLibExampleHandle;
Click to see full example
class FPLUGIN_EXAMPLEModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
void* DynamicLibExampleHandle;
};
Then in your PLUGIN_EXAMPLE.cpp
add the following code to StartupModule
to load your library:
const FString BasePluginDir = IPluginManager::Get().FindPlugin("PLUGIN_EXAMPLE")->GetBaseDir();
const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT("Source/ThirdParty/lib_win64_example.dll"));
DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath);
if (DynamicLibExampleHandle != nullptr)
{
UE_LOG(LogTemp, Log, TEXT("lib_win64_example.dll loaded successfully!"));
}
else
{
UE_LOG(LogTemp, Fatal, TEXT("lib_win64_example.dll failed to load!"));
}
And the following code to ShutdownModule
to unload your library:
FPlatformProcess::FreeDllHandle(DynamicLibExampleHandle);
DynamicLibExampleHandle = nullptr;
Click to see full example
void FPLUGIN_EXAMPLEModule::StartupModule()
{
const FString BasePluginDir = IPluginManager::Get().FindPlugin("PLUGIN_EXAMPLE")->GetBaseDir();
const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT("Source/ThirdParty/lib_win64_example.dll"));
DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath);
if (DynamicLibExampleHandle != nullptr)
{
UE_LOG(LogTemp, Log, TEXT("lib_win64_example.dll loaded successfully!"));
}
else
{
UE_LOG(LogTemp, Fatal, TEXT("lib_win64_example.dll failed to load!"));
}
}
void FPLUGIN_EXAMPLEModule::ShutdownModule()
{
FPlatformProcess::FreeDllHandle(DynamicLibExampleHandle);
DynamicLibExampleHandle = nullptr;
}
You then need to include the header files in the file you want to call the library functions from. To remove the various warnings/errors that might cause the build to fail, you can wrap your include with the THIRD_PARTY_INCLUDES_START
and THIRD_PARTY_INCLUDES_END
macros:
THIRD_PARTY_INCLUDES_START
#include "ThirdParty/header_to_include.h"
THIRD_PARTY_INCLUDES_END
If there is only a dynamic library
If you only have a dynamic library, you will need to manually import the required functions.
In your build.cs
file, you need to add your dynamic library as a runtime dependency to put it along with the executable:
RuntimeDependencies.Add("$(PluginDir)/ThirdParty/lib_win64_example.dll");
Click to see full example
using UnrealBuildTool;
public class PLUGIN_EXAMPLE : ModuleRules
{
public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Projects"
}
);
// Put the library along with the executable
RuntimeDependencies.Add("$(PluginDir)/ThirdParty/lib_win64_example.dll");
}
}
The next step is to specify when to load and free your dynamic library. In the following example, the lifetime of the library will match the lifetime of the module. That is, the library will be loaded on module startup and freed on module shutdown.
You need to open your module’s interface file PLUGIN_EXAMPLE.h
and add a field that will contain your dynamic library handler:
void* DynamicLibExampleHandle;
Click to see full example
class FPLUGIN_EXAMPLEModule : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
void* DynamicLibExampleHandle;
};
Then in your PLUGIN_EXAMPLE.cpp
add the following code to StartupModule
to load your library:
const FString BasePluginDir = IPluginManager::Get().FindPlugin("PLUGIN_EXAMPLE")->GetBaseDir();
const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT("Source/ThirdParty/lib_win64_example.dll"));
DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath);
if (DynamicLibExampleHandle != nullptr)
{
UE_LOG(LogTemp, Log, TEXT("lib_win64_example.dll loaded successfully!"));
}
else
{
UE_LOG(LogTemp, Fatal, TEXT("lib_win64_example.dll failed to load!"));
}
And the following code to ShutdownModule
to unload your library:
FPlatformProcess::FreeDllHandle(DynamicLibExampleHandle);
DynamicLibExampleHandle = nullptr;
Click to see full example
void FPLUGIN_EXAMPLEModule::StartupModule()
{
const FString BasePluginDir = IPluginManager::Get().FindPlugin("PLUGIN_EXAMPLE")->GetBaseDir();
const FString LibExamplePath = FPaths::Combine(*BasePluginDir, TEXT("Source/ThirdParty/lib_win64_example.dll"));
DynamicLibExampleHandle = FPlatformProcess::GetDllHandle(*LibExamplePath);
if (DynamicLibExampleHandle != nullptr)
{
UE_LOG(LogTemp, Log, TEXT("lib_win64_example.dll loaded successfully!"));
}
else
{
UE_LOG(LogTemp, Fatal, TEXT("lib_win64_example.dll failed to load!"));
}
}
void FPLUGIN_EXAMPLEModule::ShutdownModule()
{
FPlatformProcess::FreeDllHandle(DynamicLibExampleHandle);
DynamicLibExampleHandle = nullptr;
}
You then need to include the header files in the file you want to call the library functions from. To remove the various warnings/errors that might cause the build to fail, you can wrap your include with the THIRD_PARTY_INCLUDES_START
and THIRD_PARTY_INCLUDES_END
macros:
THIRD_PARTY_INCLUDES_START
#include "ThirdParty/header_to_include.h"
THIRD_PARTY_INCLUDES_END
To properly get the required functions to call from your dynamic library, you need to use FPlatformProcess::GetDllExport
. To make it easier to get functions, you can create the following macro:
CALL_LIBEXAMPLE_FUNC(FunctionName) \
[]() { \
using FunctionType = decltype(&FunctionName); \
const FName ModuleName = FName(TEXT("PLUGIN_EXAMPLE")); \
const FPLUGIN_EXAMPLEModule& CurrentModule = FModuleManager::GetModuleChecked<FPLUGIN_EXAMPLEModule>(ModuleName); \
static FunctionType FunctionPtr = (FunctionType)(FPlatformProcess::GetDllExport(CurrentModule.DynamicLibExampleHandle, TEXT(#FunctionName))); \
return FunctionPtr; \
}()
And then use it as follows:
// This is an example of an argument to pass to a function
bool ArgumentToPass = true;
// Let's say there is a function "getInvertedBool(bool)" in the dynamic library
CALL_LIBEXAMPLE_FUNC(getInvertedBool)(ArgumentToPass);
Support different platforms
Here is the example of static library integration with support for Win64, Linux and Mac platforms. You can easily modify it to suit your needs to support the required platforms.
using System;
using System.IO;
using UnrealBuildTool;
public class PLUGIN_EXAMPLE : ModuleRules
{
private string GetExampleLibraryPath(ReadOnlyTargetRules Target)
{
if (Target.Platform == UnrealTargetPlatform.Win64)
{
return Path.Combine(ModuleDirectory, "..", "ThirdParty", "lib_win64_example.lib");
}
if (Target.Platform == UnrealTargetPlatform.Linux)
{
return Path.Combine(ModuleDirectory, "..", "ThirdParty", "lib_linux_example.a");
}
if (Target.Platform == UnrealTargetPlatform.Mac)
{
return Path.Combine(ModuleDirectory, "..", "ThirdParty", "lib_mac_example.a");
}
return null;
}
public PLUGIN_EXAMPLE(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Core"
}
);
string LibraryPath = GetExampleLibraryPath(Target);
if (LibraryPath != null)
{
PublicAdditionalLibraries.Add(LibraryPath);
}
}
}