How do you statically link an external DLL/dylib to your project?

Dear sirs,

I have ready all the example projects on github (eg. GitHub - BobGneu/Flathead: Flathead = UE4 + Javascript) and the linking guide (A new, community-hosted Unreal Engine Wiki - Announcements - Unreal Engine Forums) but I find myself no closer to understanding how the plugin system works, or how to link and external library.

Please help!

Kindest regards.

Funny you should ask!

As it happen, I just spent a whole week trying to understand this, and I finally have it figured out.

The TLDR; is, go here, look at this: GitHub - shadowmint/ue4-static-plugin: A sample static plugin for the unreal engine that imports a C++ and rust library and links them statically.

The longer version is:

Yes, the static guide isn’t very helpful, but it covers the basic things you need to do:

  1. Have a set of local .cpp files in your plugin

  2. Call PublicAdditionalLibraries.Add(full_library_path);

  3. Call PublicIncludePaths.Add(full_include_path);

Notice (1). (1) is important. See, plugins are actually compiled into dynamic libraries that sit in the Plugins/Foo/Binaries folder, and are linked into your project.

If you have no local .cpp files, no matter how many external dependencies you have, you never trigger the UBT to create a local shared library that references all those tasty external symbols you need.

Finally, also note that the UBT silently consumes errors. You will almost certainly want to go and apply this patch:

diff --git a/Engine/Source/Programs/UnrealBuildTool/Mac/MacToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Mac/MacToolChain.cs
index 582fef0..9f5336f 100644
--- a/Engine/Source/Programs/UnrealBuildTool/Mac/MacToolChain.cs
+++ b/Engine/Source/Programs/UnrealBuildTool/Mac/MacToolChain.cs
@@ -149,6 +149,7 @@ namespace UnrealBuildTool
                {
                        string Result = "";

+                       Result += " -v";
                        Result += " -fmessage-length=0";
                        Result += " -pipe";
                        Result += " -fpascal-strings";
@@ -602,6 +603,7 @@ namespace UnrealBuildTool

                public override FileItem LinkFiles(LinkEnvironment LinkEnvironment, bool bBuildImportLibraryOnly)
                {
+      Log.TraceError("Performing a build");
                        bool bIsBuildingLibrary = LinkEnvironment.Config.bIsBuildingLibrary || bBuildImportLibraryOnly;

                        // Create an action that invokes the linker.
@@ -622,6 +624,7 @@ namespace UnrealBuildTool

                        string Linker = bIsBuildingLibrary ? MacArchiver : MacLinker;
                        string LinkCommand = "xcrun " + Linker + VersionArg + " " + (bIsBuildingLibrary ? GetArchiveArguments_Global(LinkEnvironment) : GetLinkArguments_Global(LinkEnvironment));
+      Log.TraceError(LinkCommand);

                        // Tell the action that we're building an import library here and it should conditionally be
                        // ignored as a prerequisite for other actions
@@ -886,6 +889,7 @@ namespace UnrealBuildTool
                        // Only execute linking on the local Mac.
                        LinkAction.bCanExecuteRemotely = false;

+      Log.TraceError(LinkCommand);
                        LinkAction.StatusDescription = Path.GetFileName(OutputFile.AbsolutePath);
                        LinkAction.OutputEventHandler = new DataReceivedEventHandler(RemoteOutputReceivedEventHandler);

To have it verbosely log the output of builds, so you can see what’s going on on OSX. On windows, you’re basically screwed. Good luck!

What would be the difference between this and say… PublicDependencyModuleNames. These are statically linked as well are they not?

I’m going to go out on a limb, and guess that these static libs that you are referring to are not already built as modules?

Also, if this does what I think it does, it shouldn’t be static linking only. It should link to shared libs as well. Both are “compile time” linkage but one copies code and the other just links it.

You are correct; this is for compiled external symbols; ie. .a or .lib static library file you have compiled already, with say, cmake.

The PublicDependencyModuleNames is a list of modules that the engine must find and compile itself.

The key difference here is that if you have a 3rd party library that you do not possess the source for, for example, the leap motion, you cannot link it as a dependent module.

However, as you can see from the example, you are forced to manually compile these external symbols for the build target, rather than relying on the UBT to correctly provide the link flags, etc. for the various platforms.

So, all in all, unless you have no alternative, writing a ‘Plugin’ for a 3rd party library, that uses the UBT to compile the library is categorically superior to linking statically to an externally built library… but sometimes its unavoidable.

Also, you would, of course, assume that linking a dynamic library would work right?

Haha! No.

See, what happens is a plugin is compiled as a dynamic library; and if it links to a dynamic library, your dependency chain looks like this:

MyProject <>---- MyPlugin <>---- libvpx

Guess which symbols from libvpx are usable from MyProject? Only the symbols that are actually used from MyPlugin. Seems obvious, and no problem? Practically speaking, it’s a real pain.

You have to manually export the library symbols in your plugin, or write a wrapper for them explicitly. This means you cannot do this:

MyProject <>---- My3rdPartyDeps <>— libfoo, libvpx, libcereal, …

And then directly use those libraries from your project.

Statically linking overcomes this problem; but indeed, you can use dynamic linking for the same effect, if you want to. …but I warn you now, I strongly recommend against it. I’ve directly seen 3 different gamejam groups go down this road and crash and burn directly because of it. /shrug

1 Like

@shadowmint, I’ve stumbled across this while searching down many a rabbit hole. I’m trying to set up a build system that combines UE4 and my CMake targets. I’ve got it all working except for the fact that UBT doesn’t seem to re-link when the .libs specified via PublicAdditionalLibraries have been updated since the last compilation. Have you ever seen such behaviour working?

+1 to 's question. PublicAdditionalLibraries.Add(sharedobjectPath); doesn’t work for me, and while packaging i get errors:

error: function ‘MyFunctionNameInsideSO’ is not defined [-Werror,-Wundefined-inline]