サードパーティ ライブラリ

サードパーティ ライブラリを Unreal Engine に統合する方法について説明します。

このドキュメントでは、ライブラリを追加するための標準パターン、動的ライブラリに関して特に考慮すべき事項、依存関係のステージング、Unreal プロジェクトにサードパーティ ライブラリを統合する際に発生する可能性のあるエラーに関する有益な情報など、サードパーティ ライブラリを統合する方法について説明します。

Unreal Engine (UE) のソース コードにはサードパーティ ライブラリが含まれています。サードパーティ ライブラリは「UnrealEngine/Engine/Source/ThirdParty/..」の下に保存されています。このパスはエンジン モジュールの規則に沿って設定されていますが、必ずこのパスを使用する必要があるわけではありません。サードパーティ ライブラリを使用してプラグインを開発する場合は、プラグイン ディレクトリ内にサードパーティ ソフトウェアを含めると役立ちます。

サードパーティ プラグインのテンプレート

エディタのプラグイン ブラウザには、サードパーティ ライブラリを統合するためのテンプレートがあります。これを使用して新しいプラグインを作成するには、プラグイン ブラウザ ウィンドウから [New Plugin (新規プラグイン)] を選択して、「Third Party Plugin」 テンプレートまでスクロールします。

モジュールを設定する

通常の Unreal Engine C++ モジュールは「.build.cs」ファイルを使用して設定しますが、サードパーティ ライブラリも同様です。ソース コードを一切含まず、他のモジュールで使用するために存在するモジュールを作成するには、次のようにプラグイン用の「.build.cs」を作成します。

    using System;
    using System.IO;
    using UnrealBuildTool;

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

            // Add any macros that need to be set
            PublicDefinitions.Add("WITH_MYTHIRDPARTYLIBRARY=1");

            // Add any include paths for the plugin
            PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "inc"));

            // Add any import libraries or static libraries
            PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "lib", "foo.a"));
        }
    }

.build.cs」は、「Engine/Source」や「MyProject/Source」など、エンジンがモジュールを検索する通常のフォルダにある必要があります。

ModuleType.External の設定で、エンジンがソース コードを検索 (またはコンパイル) しないように指定することができます。この設定では、リストされたインクルード パスをコンパイル環境に追加し、適切なマクロを設定し、指定されたスタティック ライブラリに対してリンクすることで、そのファイルで定義した他の設定を使用します。

動的ライブラリ

Windows

Windows にあるモデルは、DLL のロードに対してほとんど柔軟性がありません。各実行可能ファイルまたは DLL は、依存する DLL のリストを import table に保存し、OS はモジュールをロードするときにこれらの依存関係を適合させるためにこのリストをスキャンします。

依存関係のある DLL の名前は名前のみで保存される (例えば、パス情報は含まれない) ため、オペレーティング システムは、パスの短いリストを検索して DLL の検出を試行します。これは、アプリケーションが依存関係のある DLL の場所を指定できなくても実行されることから、起動時に原因不明のエラーが発生する可能性があります。

FPlatformProcess::GetDllHandle

エンジンによって明示的にトリガーされる DLL ロードはすべて、「FPlatformProcess::GetDllHandle()」関数を使用します。この関数には、ロードする前に各 DLL インポート テーブルを読み取る特殊なロジックがあり、エンジンの検索パスのリストにあるファイル (すべてのプロジェクト、エンジン、プラグインの「Binaries」ディレクトリなど) への、DLL の依存関係の解決を試行します。

オペレーティング システムが新しい DLL をロードするときに、同じ名前の DLL がすでにメモリにある場合は、その DLL に対してリンクを作成します。ディスクから新しいモジュールをロードするわけではありません。

GetDllHandle 関数では、依存関係をロードできなかった場合、ログに詳細出力を大量に生成します。これは、エラーの追跡をする際にとても役立ちます。

DLL のロードの遅延

エンジンが通常は検索しないパスに DLL がある場合、代わりの方法として DLL のロードを遅らせることができます。オペレーティング システムは DLL 内の関数を最初に呼び出すときにのみ DLL のロードを試行するため、明示的にロジックを実行して特定の場所から DLL をロードできます。OS が最終的にロードを遅延させた場合、OS はプロセスにすでにロードされている同じ名前の既存の DLL を探し、ディスク上での検索は行わずにその DLL を解決します。

インポートされた関数を実際の DLL をロードするサンク関数にポイントすることで、ロードの遅延が実行されます。実際の DLL がロードされた後、インポート テーブルはサンク関数ではなく実際の DLL 関数のアドレスをポイントするように変更され、通常どおり実行が継続されます。

DLL の変数にアクセスする場合は、この方法は使用できません。参照されている DLL のロードをこの方法で遅延させようとすると、リンカからエラーが返されます。

ロードの遅延を実行する DLL は、次のような宣言を使用して「build.cs」ファイルから指定できます。

    PublicDelayLoadDLLs.Add("foo.dll")

リンカ自体は、オペレーティング システムがインポート テーブルに追加する DLL の依存関係をどのように解決するのかについて関与しないため、DLL へのパスは不要です (名前のみが使用されます)。

DLL のロード時の問題をデバッグする

Dependency Walker は、モジュールからインポートされた DLL および関数を調査するのに役立ちます。エンジンのログの詳細出力も「Dependency Walker」ツールを備えています。

macOS

macOS 実行可能ファイルと dylib は、それらが依存関係にある dylib のリストをインストール名の形式で保存します。インストール名は、dylib への絶対パスまたは相対パス、または「@executable_path」、「@load_path」、「@rpath」の 3 つのインストール パスのいずれかへの相対パスにすることができます。これらの中で最も柔軟性があるのは「@rpath」で、Unreal Engine ではこのパスを使用します。

UnrealBuildTool は、RPATH 検索パスを、「Engine/Source」および「MyProject/Source」サブフォルダの外部にあるすべてのサードパーティ dylib 用にビルドする実行可能ファイルと dylib に自動的に追加します。サードパーティの dylib の Source サブフォルダへの保存はサポートされていますが、推奨されていません。これらのフォルダはパッケージ化されたゲームまたはプラグインのバイナリ バージョンの一部ではなく、ビルド システムはそれらを異なる方法で処理する必要があります。特に、別の場所にコピーする必要があるためです。

リンク時にインストール名が dylib から読み取られるため、サードパーティ ライブラリのインストール名は「@rpath/libfoo.dylib」に設定する必要があります。これを行うには、ライブラリをビルドするときに「-install_name」リンカ オプションを使用する方法と、dylib の作成後に install_name_tool を使用して次のように変更する方法があります。

    install_name_tool -id @rpath/libfoo.dylib /path/to/libfoo.dylib

フレームワークの場合、「.build.cs」ファイルで PublicAdditionalLibraries の代わりに PublicFrameworks を使用する必要がありますが、それ以外は同じルールが適用されるため、フレームワークのインストール名にも「@rpath」を使用することをお勧めします。

DLL のロードの遅延

DLL のロードの遅延は macOS では完全にサポートされていません。

Windows の「/DELAYLOAD」に相当するものがないことから、macOS ではこの機能の特定用途の 1 つである、実行時に利用できない可能性のあるライブラリとのリンクのみがサポートされています。その実装のため、UE では弱いリンクを使用しています。

Dylib のロード時の問題をデバッグする

コマンド ラインから実行可能ファイルまたは dylib を確認し、実行時の依存関係を確認するには、otool が便利です。特に「-L」オプションと「-l」オプションは、ロードの問題をデバッグするときに役立ちます。

コマンド

説明

otool -L libname.dylib

実行可能ファイルまたは dylib が依存関係にあるすべての dylib のインストール名とバージョン番号をリストします。dylib の場合、リストの最初のアイテムは独自のインストール名です。

otool -l libname.dylib

ファイルのすべてのロード コマンドを表示します。LC_LOAD_DYLIBLC_RPATH を検索して、動的リンカが自動的にロードを試みるものと、RPATH 検索パスを確認できます。

Linux

dlopen が実行時に検索するパスを変更する方法はないため、Linux を検索するすべてのパスは、各モジュールの RPATH の下に設定されます。readelf を使用すると、検索される完全な RPATH リストを確認できます。

FPlatformProcess::GetDllHandle

すべての Unreal Engine モジュールは、「RTLD_LAZY| RTLD_LOCAL」として dlopen を使用して開かれる一方で、UE 以外のモジュールはすべて最初に「LAZY| LOCAL」としてロードされ、その後、「LAZY| RTLD_GLOBAL」として再度開かれます。これにより特定の問題が発生します。これは、複数の UE モジュールが同じグローバル シンボルを持つことで、UE モジュールが単一のグローバル シンボルではなく、ローカル シンボルにリンクし、グローバル システムが初期化されていないようにみえるという変則的なクラッシュにつながるという問題です。gdb を使用すると、格納されていると思われる場所と、グローバルにあるポインタを出力できます。それらが異なる場合、多くの場合、複数のグローバル定義とモジュールが異なるものにバインディングされています。

SO ロード時の問題をデバッグする

ツール

Linux のマニュアル ページ

簡単な説明

ldd

http://man7.org/linux/man-pages/man1/ldd.1.html

実行時の依存関係と、不足している可能性のある依存関係がわかります。

nm

http://man7.org/linux/man-pages/man1/nm.1p.html

「Dependency Walker」に似ているツールで、エクスポートされたシンボルを確認できます (必要な場合、すべてのシンボルも確認できます)。

readelf

http://man7.org/linux/man-pages/man1/readelf.1.html

エクスポートされたシンボルと elf セクションのオフセットなどに関する情報をダンプするもう 1 つのツールです。

LD_DEBUG

http://man7.org/linux/man-pages/man8/ld.so.8.html

どのシンボルがどの動的ライブラリにバインドされているかを判断するもう 1 つの方法です。

strace

http://man7.org/linux/man-pages/man1/strace.1.html

使用されている実行時システム コールを把握するのに役立つツールです。このツールでは、dlopen のオープン / 読み取りを試行しているパスを確認できます。

実行時依存関係

ゲームをパッケージ化する際、実行可能ファイルの隣にサードパーティ DLL をステージングするために、「build.cs」ファイルの実行時依存関係としてその DLL を次のように宣言できます。

    RuntimeDependencies.Add(Path.Combine(PluginDirectory, "Binaries/Win64/Foo.dll"));

この宣言は、DLL が指定されたディレクトリにすでに存在し、プラグインがその場所から手動でその DLL をロードすることを前提としています。ビルド時に DLL を実行可能ファイルと同じ出力ディレクトリにコピーする場合は、「RuntimeDependencies.Add」メソッドのオーバーロードを介して次のように行います。

    RuntimeDependencies.Add("$(TargetOutputDir)/Foo.dll", Path.Combine(PluginDirectory, "Source/ThirdParty/bin/Foo.dll"));

DLL の出力パスには他の変数を使用できます。

変数

説明

$(EngineDir)

エンジンのディレクトリ

$(ProjectDir)

プロジェクト ファイルを含むディレクトリ

$(ModuleDir)

.build.cs」ファイルを含むディレクトリ

$(PluginDir)

.uplugin」ファイルを含むディレクトリ

$(BinaryOutputDir)

このモジュールがコンパイルされるバイナリを含むディレクトリ (エディタ ビルド用の DLL へのパス、パッケージ ビルド用の実行可能ファイル (EXE) へのパスなど)

$(TargetOutputDir)

実行可能ファイルを含むディレクトリ (エディタ ビルドを含む)

RuntimeDependencies フィールドは、ステージング DLL に限定されません。つまり、このフィールドを使用して追加のファイルをステージング プロセスに挿入することもできます。これらのファイルは、Unreal の PAK ファイル に保存するか、ルーズ ファイルとしてディスク上に保持することができます。DLL はオペレーティング システムによってロードされるため、通常は PAK ファイルに保存できません。

    RuntimeDependencies.Add(Path.Combine(PluginDirectory, "Extras/..."), StagedFileType.UFS);

StagedFileType に対して使用可能な値は次のとおりです。

説明

StagedFileType.UFS

Unreal ファイルシステム関数を介してのみアクセスできます。また、PAK ファイルに含まれている場合があります。

StagedFileType.NonUFS

これは、ルーズ ファイルシステムの一部として保持する必要があります。

StagedFileType.DebugNonUFS

ルーズ ファイルシステムの一部として保持する必要があるデバッグ ファイル。デバッグ ファイルがステージングされるように設定されていない限り、これは含まれません。

StagedFileType.SystemNonUFS

ルーズ ファイルシステムの一部として保持する必要があるシステム ファイル。システム ファイルは、プラットフォーム レイヤの自動再マッピングまたは名前変更の対象ではありません。

トラブルシューティング

Windows.h

標準の Windows ヘッダ (「Windows.h」) は、ほとんどの場合 UE コードに含まれていません。サードパーティ ライブラリに標準の Windows ヘッダが必要な場合は、Core モジュールの「WindowsHWrapper.h」ファイルを介して含めるようにしてください。

    #include "Windows/WindowsHWrapper.h"

多くの Windows 関数は、ANSI と Unicode のバリアントを切り替えるマクロとして定義されており、無関係のコードが同じ名前のシンボルを定義している場合、問題が発生する可能性があります。これを防ぐために、Unreal Engine では、「Windows.h」で定義された多くのマクロの定義が解除されています。可能な場合は、関数の「... A」および「 ... W」バリアントを明示的に呼び出すことをお勧めします (「GetCommandLineA()」または「 GetCommandLineW()」など)。

TRUE および FALSE の Windows マクロは移植性がなく、使用するとコンパイル エラーが発生する値に再定義されます。コード ブロックでそれらを有効にする必要がある場合は、次のように、「AllowWindowsPlatformTypes.h」および「 HideWindowsPlatformTypes.h」のインクルードでラップします。

    #include "Windows/AllowWindowsPlatformTypes.h"
    int Foo = TRUE;
    #include "Windows/HideWindowsPlatformTypes.h"

同様に、アトミック関数の Windows マクロは、「WindowsPlatformAtomics.h」で定義されている関数名と競合します。これらの定義を元の値に戻すには、次のように、「AllowWindowsPlatformAtomics.h / HideWindowsPlatformAtomics.h」をインクルードします。

    #include "Windows/AllowWindowsPlatformAtomics.h"
    //Code using InterlockedIncrement, ...
    #include "Windows/HideWindowsPlatformAtomics.h"

C++ での警告とエラー

Unreal Engine のコードベースには、デフォルトでエラーとして処理される多くの警告があります。サードパーティ コードのこれらの制限の一部を緩和するために、一般的な警告を一時的に無効にする次のクロスプラットフォーム マクロがあります。

    THIRD_PARTY_INCLUDES_START
    #include <openssl.h>
    THIRD_PARTY_INCLUDES_END

デフォルトのパッキングとアライメント

従来のシステムに対応するため、Unreal Engine では Win32 で 4 バイトのパッキングを強制しています。これにより、double や long などの 8 バイト型を使用するクラスでは、デバッグが困難なアライメントの問題が発生する可能性があります。パブリック構造体で 8 バイト型を定義するサードパーティ コードをデフォルトのパッキングに戻すには、次のマクロを使用します。

    PRAGMA_PUSH_PLATFORM_DEFAULT_PACKING
    #include <thirdparty.h>
    PRAGMA_POP_PLATFORM_DEFAULT_PACKING

RTTI ビルド エラー

異なる RTTI (実行時型情報) フラグでコンパイルされたソース ファイルからバイナリをリンクすると、Windows でビルド エラーが発生する場合があります。RTTI ビルド エラーが発生した場合は、ヘルパー マクロを定義して RTTI オン / オフ モジュールを両方追加します。ソースからビルドする場合は、「TargetRules.cs」で bForceEnableRTTItrue に設定することでエンジン全体で RTTI を有効にできます。

Linux

Linux では、RTTI モジュールを組み合わせて使用することはできません。そのため、RTTI を備えたモジュールがある場合は、エンジンに対して有効にする必要があります。

動的キャストのエラー

RTTI がオフのときに UObject 型ではないオブジェクトの型でダイナミック キャストを使用すると、「RTTI が無効になっているときに dynamic_cast を使用できません」というエラーが発生します。これは、「CoreUObject/Public/Templates/Casts.h」では、「#define dynamic_cast」が CUObject モジュールをリダイレクトするためです。UObject 型の場合、ダイナミック キャストは Unreal Engine のリフレクション システムを使用しますが、他の型の場合は通常の dynamic_cast を使用します。

Unreal Engine のドキュメントを改善するために協力をお願いします!どのような改善を望んでいるかご意見をお聞かせください。
調査に参加する
キャンセル