Adding custom third-party library to plugin from scratch

In this article we will create custom dll project (emulating third-party library), custom plugin , connect plugin with dll project and add that plugin to blank project

Updated over 2 years ago Edit Page Revisions

Preparation and setup

Create an empty C ++ project with no starter content and name it PluginTestProject. In my case, the project is located in D:\Workspace\PluginTestProject image.png

In VS, the project should appear in the Games\PluginTestProject subfolder. We run it to make sure everything is in order. Create your plugin Edit->Plugins->NewPlugin we will create everything from scratch, so choose Blank, but you can also choose Third Party Library. Name the plugin AwesomePlugin. We want to create a plugin for our application and not for the whole engine, so we uncheck Is Engine Plugin image.png

The directory looks like this image.png

DLL-project

Create folder PluginTestProject\Plugins\AwesomePlugin\Source\ThirdPartyLibraries we will add third-party libraries to it. Create DLL-project MyAwesomeLibrary inside ThirdPartyLibraries image.png

We immediately go to the properties of the VS solution and delete all configurations except Release and remove target platforms except x64 image.png

VS solution structure looks like this image.png

I don't need precompiled headers, so I disable them in
MyAwesomeLibrary->Properties->C/C++->Precompiled Headers->Use
and after that I delete all .cpp and .h files in the project. Create files AwesomeClass.h and AwesomeClass.cpp and add code

AwesomeClass.h

#pragma once
#if defined _WIN64
    #include <string>
    #define AWESOMECLASS_IMPORT __declspec(dllimport)
#else
    #define HELLOWORLD_IMPORT
#endif
AWESOMECLASS_IMPORT std::string PrettyPrint();

AwesomeClass.cpp

#if defined _WIN64
#pragma once
    #include <string>
    #define AWESOMECLASS_EXPORT __declspec(dllexport)
#endif
#ifndef AWESOMECLASS_EXPORT
    #define AWESOMECLASS_EXPORT
#endif
#include "AwesomeClass.h"
AWESOMECLASS_EXPORT std::string PrettyPrint()
{
    return { "Hi from AwesomeClass\n" };
}

Let's compile the project and make sure there are no errors. Let's add an additional project to the VS MyAwesomeLibrary solution - the MyAwesomeLibraryTest console application. The structure of the project is as follows image.png

Add the test code to the MyAwesomeLibraryTest.cpp file.

MyAwesomeLibraryTest.cpp

#include <iostream>
#include "../MyAwesomeLibrary/AwesomeClass.h"
int main()
{
    std::cout << "Hello World!\n";
    std::cout << PrettyPrint();
    system("pause");
}

When compiling, a linker will spills errors at you. The MyAwesomeLibraryTest project needs to be told where to find the .lib files, so add the path $(SolutionDir)\x64\Release in
MyAwesomeLibraryTest->Linker->General->AdditionalLibraryDirectories Adding file name MyAwesomeLibrary.lib in MyAwesomeLibraryTest->Linker->Input->AdditionaDependencies The project compiles and the message is displayed in the console image.png

At the root of the solution create a file MyAwesomeLibrary.Build.cs and fill it

MyAwesomeLibrary.Build.cs

using System;
using System.IO;
using UnrealBuildTool;
public class MyAwesomeLibrary : ModuleRules
{
    public MyAwesomeLibrary(ReadOnlyTargetRules Target) : base(Target)
    {
        Type = ModuleType.External;
        if (Target.Platform == UnrealTargetPlatform.Win64)
        {
            // Add the import library
            PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "x64", "Release", "MyAwesomeLibrary.lib"));
            // Delay-load the DLL, so we can load it from the right place first
            PublicDelayLoadDLLs.Add("MyAwesomeLibrary.dll");
            // Ensure that the DLL is staged along with the executable
            RuntimeDependencies.Add("$(PluginDir)/Binaries/ThirdParty/MyAwesomeLibrary/Win64/MyAwesomeLibrary.dll");
        }
    }
}

With the last line we said that .dll files should be looked for in the plugin directory in the Binaries folder. Let's create such a folder structure image.png

In order not to copy the .dll files along this path after each code change and new compilation, add the console command
copy "$(TargetPath)" "$(SolutionDir)..\..\..\Binaries\ThirdParty\MyAwesomeLibrary\Win64"
in project settings
MyAwesomeLibrary->Properties->Build Events->Post-Build Event->Command Line
Such command will copy files after each build

Connect UE and plugin

We added the Build.cs file to the project, but unreal doesn't know about it yet. The project files should be updated (GenerateProjectFiles.bat or RMB on the .uproject file and Generate Visual Studio Project Files) Unreal project structure looks like this image.png

Add changes to AwesomePlugin.h and AwesomePlugin.cpp

AwesomePlugin.h

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"

class FAwesomePluginModule : public IModuleInterface
{
public:
    /** IModuleInterface implementation */
    virtual void StartupModule() override;
    virtual void ShutdownModule() override;
private:
    /** Handle to the test dll we will load */
    void* MyAwesomeLibraryHandle;
};

AwesomePlugin.cpp

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AwesomePlugin.h"
#include "Core.h"
#include "Modules/ModuleManager.h"
#include "Interfaces/IPluginManager.h"
#include "ThirdPartyLibraries/MyAwesomeLibrary/MyAwesomeLibrary/AwesomeClass.h"
#define LOCTEXT_NAMESPACE "FAwesomePluginModule"

void FAwesomePluginModule::StartupModule()
{
    // 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("AwesomePlugin")->GetBaseDir();
    // Add on the relative location of the third party dll and load it
    FString MyAwesomeLibraryPath = FPaths::Combine(*BaseDir, TEXT("Binaries/ThirdParty/MyAwesomeLibrary/Win64/MyAwesomeLibrary.dll"));
    MyAwesomeLibraryHandle = !MyAwesomeLibraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*MyAwesomeLibraryPath) : nullptr;
    if (MyAwesomeLibraryHandle)
    {
         // Some code that will be executed if dll was found
    }
    else
    {
        FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ThirdPartyLibraryError", "Failed to load MyAwesomeLibrary"));
    }
}

void FAwesomePluginModule::ShutdownModule()
{
    // This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
    // we call this function before unloading the module.
    // Free the dll handle
    FPlatformProcess::FreeDllHandle(MyAwesomeLibraryHandle);
    MyAwesomeLibraryHandle = nullptr;
}

#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FAwesomePluginModule, AwesomePlugin)

In AwesomePlugin.Build.cs add parameter ”MyAwesomeLibrary” to PublicDependencyModuleNames

PublicDependencyModuleNames.AddRange(new string[]{"Core","Projects", "MyAwesomeLibrary"}); 

In order to add plugin to project the plugin name should be added in PluginTestProject.Build.cs file

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "AwesomePlugin"});
PrivateDependencyModuleNames.AddRange(new string[] { "AwesomePlugin" });

Functional check

Let's start the project and create the TestActor class in the plugin environment image.png

TestActor.h

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TestActor.generated.h"

UCLASS()
class AWESOMEPLUGIN_API ATestActor : public AActor
{
    GENERATED_BODY()
public:        
    ATestActor();
protected:
    virtual void BeginPlay() override;
};

TestActor.cpp

#include "../Public/TestActor.h"
THIRD_PARTY_INCLUDES_START
#include "MyAwesomeLibrary/MyAwesomeLibrary/AwesomeClass.h"
THIRD_PARTY_INCLUDES_END

ATestActor::ATestActor()
{
     PrimaryActorTick.bCanEverTick = false;
}

void ATestActor::BeginPlay()
{
    Super::BeginPlay();
    GEngine->AddOnScreenDebugMessage(-1, 10.0, FColor::White, FString(PrettyPrint().c_str()));
}

Let's compile and add this AActor to the scene. Keep in mind that any changes to the plugin code require a restart of the engine. image.png And after starting game we see... image.png Success!