How should a third-party C/C++ library be compiled from source with Flax.Build?

Hi,

We’re trying to integrate a third-party native library into our game and compile its sources directly with Flax.Build. But, we’re not sure what the intended approach is, especially when using a ThirdPartyModule.

Our concrete use case is TinyEXR v1.0.13. It is mostly a single-header C++ library, with Miniz as a C dependency.

We currently have something roughly like this:

public class GameTinyEXR : ThirdPartyModule
{
    public override void Init()
    {
        base.Init();

        LicenseType = LicenseTypes.BSD3Clause;
        LicenseFilePath = Path.Combine(
            "..", "..", "ThirdParty", "tinyexr", "LICENSE"
        );
    }

    public override void Setup(BuildOptions options)
    {
        base.Setup(options);

        var tinyexrPath = Path.Combine(
            FolderPath, "..", "..", "ThirdParty", "tinyexr"
        );

        options.PublicIncludePaths.Add(tinyexrPath);

        var minizPath = Path.Combine(
            tinyexrPath, "deps", "miniz"
        );

        options.PrivateIncludePaths.Add(minizPath);
        options.PublicDefinitions.Add("TINYEXR_USE_MINIZ");

        options.SourceFiles.Add(
            Path.Combine(minizPath, "miniz.c")
        );
    }
}

The module also contains a TinyEXR.cpp translation unit:

#define TINYEXR_IMPLEMENTATION
#include <tinyexr.h>

Our Game module has a private dependency on GameTinyEXR.

The sources themselves compile successfully with MSVC. However, with a clean/rebuild, Flax.Build tries to link Game.dll before the third-party module has produced its import library:

TinyEXR.cpp
miniz.c
...
Linking .../Game.dll
LINK : fatal error LNK1104: cannot open file '.../GameTinyEXR.lib'

Linking .../GameTinyEXR.dll
   Creating library .../GameTinyEXR.lib

A non-clean incremental build can hide this because the .lib from a previous build already exists.

While looking through the Flax sources, we’ve noticed that Flax’s own TinyEXR module contains:

// Merge third-party modules into engine binary
BinaryModuleName = "FlaxEngine";

We therefore tried the equivalent:

BinaryModuleName = "Game";

However, GameEditorTarget uses TargetLinkType.Modular, and Flax.Build then throws:

Exception: Cannot output binary if it uses multiple modules.

This corresponds to the following code in Builder.NativeCpp.cs:

case TargetLinkType.Modular:
{
    // Every module produces own set of binaries
    if (binaryModule.Count() != 1)
        throw new Exception(
            "Cannot output binary if it uses multiple modules."
        );
    ...
}

We’ve also noticed that the documentation example for using a native C library uses the lower-level environments:

options.CompileEnv.IncludePaths.Add(...);
options.LinkEnv.InputLibraries.Add(...);
options.DependencyFiles.Add(...);

but that example appears to deal with an already-built .lib/.dll, whereas in our case we would like Flax.Build to compile the third-party sources itself.

So our questions are:

  1. What is the recommended way to compile a third-party C/C++ library from source as part of a Flax game build?
  2. Is ThirdPartyModule intended for this use case, or mainly for describing prebuilt/native dependencies?
  3. With a modular target, should a source-built third-party library produce its own DLL/import library, or is there a supported way to compile its object files directly into the consuming Game binary?
  4. What is the intended use of BinaryModuleName for third-party modules, particularly with TargetLinkType.Modular?
  5. For a small library such as TinyEXR (one implementation .cpp plus miniz.c), would the recommended approach simply be to add those sources and include paths directly to the Game module instead?

We would prefer to keep the third-party dependency isolated in its own build definition if Flax.Build supports that cleanly, rather than putting all of its build configuration directly into Game.Build.cs.

Thanks!

Could you try sth like this, which works on rebuilds too:

MyTineEXR.Build.cs:

using System.IO;
using Flax.Build;
using Flax.Build.NativeCpp;

public class MyTineEXR : GameModule
{
    public override void Init()
    {
        base.Init();

        BuildNativeCode = true;
    }

    public override void Setup(BuildOptions options)
    {
        base.Setup(options);

        options.PublicIncludePaths.Add(Path.Combine(Globals.EngineRoot, "Source/ThirdParty/tinyexr"));
    }
}

MyTinyEXR.h public header for this module (can contain C# bindings too):

#pragma once

#include "Engine/Scripting/ScriptingType.h"

API_CLASS(Static) class MYTINEEXR_API MyTinyEXR
{
    DECLARE_SCRIPTING_TYPE_MINIMAL(MyTinyEXR);

    API_FUNCTION() static void TestExr();
};

MyTinyEXR.cpp that implementation file which can expose library to game scripts:

#include "MyTinyEXR.h"

#define TINYEXR_IMPLEMENTATION
#define TINYEXR_USE_MINIZ 1
#include <tinyexr.h>
#include <miniz.cpp>

void MyTinyEXR::TestExr()
{
    // Use native api
}

Usage in game module:

options.PrivateDependencies.Add("MyTineEXR"); added to build script setup, then use it:

#include "MyTineEXR/MyTinyEXR.h"

MyTinyEXR::TestExr();

This example uses lib files from engine, but you could put source files right in the module to have them compiled for any platform with Flax build scripts configuration on top.

I used simple GameModule for that third-party, in theory ThirdPartyModule should work too.


Also, there is a simple shortcut for creating new modules in the Editor (from Source root).