UE4: バイナリー配布や実行時リンク向けのライブラリーを UE4 プロジェクトへ組み入れる方法を mecab で解説
概要
UE4 のプロジェクトの C++ コードへ UE4 とは特に関係の無い一般のライブラリーをリンクするのは少々面倒な事がある。ヘッダーオンリーなライブラリー、例えば Eigen
や stb
あるいは Boost
の一部をリンクするような場合は困らない。しかし、バイナリー配布や実行時リンク向けのライブラリー、例えば OpenCV
や pcl
などを組み入れたい場合には少々面倒。
この記事では UE4 には組み込まれていない、比較的小さくビルドも簡単で、 UE4 プロジェクトへの組み入れを試すのに手頃な mecab
を例に、具体的な手法を整理する。
Note: mecab
は日本語の形態素解析機能を提供するライブラリーですが、形態素解析については本記事の主題ではないので解説しません。
解説の範囲
mecab
のバイナリーライブラリーのビルドから↑こんなのが UE4 でテストプレイできるまで。
方法
大雑把な手順:
mecab
のソースをダウンロードmecab
をビルド- 組み入れるUE4 プロジェクト を作成(既にあるプロジェクトでも可)
- UE4 プロジェクトへ
mecab
のファイルを配置 - UE4 プロジェクトの
<project-name>.Build.cs
をいじってmecab
を扱えるように細工する
mecab
のビルドまで
ソースの入手:
- http://taku910.github.io/mecab/#download
- mecab-0.996.tar.gz 執筆時点 2018-03-13 における配布版
ビルド:
cd src
- fix: Makefile.msvc.in
X86
->X64
@DIC_VERSION@
->102
@VERSION@
->0.996
- fix: feature_index.cpp
case 't': os_ << (size_t)path
->case 't': os_ << (unsigned int)path
- fix: writer.cpp
case 'L': *os << latice->size()
->case 'L': *os << (unsigned int)latice->size()
nmake -f Makefile.msvc.in
ビルドが完了すると libmecab.dll
などのファイルが生成されています。
Note: Windows 7 64bit に MeCab (和布蕪) と Python のバインディングを導入 (2016/08/18) - Qiita を参考にしました。そのままビルドしたり、ソースではなくバイナリー配布版を入手すると x86 になります。
UE4 プロジェクトへ mecab
を組み入れる
mecab
を組み入れたい UE4 プロジェクトのディレクトリー直下に ThirdParty
ディレクトリーを生成し、次のようにサブディレクトリーを用意しましょう。 <my-project-dir>
直下です。 Sources
や Plugins
の下ではありません。
<my-project-dir> |- ThirdParty |- mecab |- Includes |- Libraries
mecab
のファイル群を配置します。
- Includes:
mecab
のsrc
の.h
ファイル群を全て放り込む - Libraries:
libmecab.lib
とlibmecab.dll
を放り込む
<my-project>.Build.cs
に細工します。
// 1. 冒頭に using を追加しておきます。 using System.IO;
// 2. ctor に public class MyProject : ModuleRules { public MyProject(ReadOnlyTargetRules Target) : base(Target) { // ... 追加する以前からある何かや今回の件とは関係の無い定義など ... // ... // ここから今回の追加部分 var base_path = Path.GetDirectoryName( RulesCompiler.GetFileNameFromType( GetType() ) ); string third_party_path = Path.Combine( base_path, "..", "..", "Thirdparty"); PublicIncludePaths.Add( Path.Combine( third_party_path, "mecab", "Includes") ); switch ( Target.Platform ) { // 他のプラットフォームにも対応したい場合は case を増やします。 // 今回は例として Win64 のみ対応します。 case UnrealTargetPlatform.Win64: PublicLibraryPaths.Add( Path.Combine( third_party_path, "mecab", "Libraries", "Win64" ) ); PublicAdditionalLibraries.Add( "libmecab.lib" ); string mecab_dll_path = Path.Combine( third_party_path, "mecab", "Libraries", "Win64", "libmecab.dll" ); RuntimeDependencies.Add( new RuntimeDependency( mecab_dll_path ) ); string binaries_directory = Path.Combine( base_path,"..","..", "Binaries", "Win64" ); if ( ! Directory.Exists( binaries_directory ) ) System.IO.Directory.CreateDirectory( binaries_directory ); string mecab_dll_destination = System.IO.Path.Combine( binaries_directory, "libmecab.dll" ); CopyFile( mecab_dll_path, mecab_dll_destination ); PublicDelayLoadDLLs.AddRange( new string[] { "libmecab.dll" } ); break; default: throw new System.Exception( System.String.Format( "Unsupported platform {0}", Target.Platform.ToString() ) ); } } // この関数も追加 private void CopyFile( string source, string destination ) { System.Console.WriteLine( "Copying {0} to {1}", source, destination ); if ( System.IO.File.Exists( destination ) ) System.IO.File.SetAttributes( destination, System.IO.File.GetAttributes( destination ) & ~System.IO.FileAttributes.ReadOnly ); try { System.IO.File.Copy( source, destination, true ); } catch ( System.Exception e ) { System.Console.WriteLine( "Failed to copy file: {0}", e.Message ); } } }
この記事に興味を持って読んでくれている方は C# の経験が豊富でなくともこの程度のコードは読めばわかると思いますので説明は省略します。
Note: Using thirdparty libraries in our UE4 mobile/desktop project – Parallelcube を参考にしました。
これで <my-project> をビルド(コンパイル)すると、 mecab
のバイナリーライブラリーも配置されプロジェクトの C++ コードから使用可能となります。
おまけ: mecab
を使う UE4/C++ コードを書いて Blueprint (UMG) から mecab
する
mecab
を Blueprint で使う UE4/C++ コードはこんな感じ:
// MyUtility.h #pragma once #include "CoreMinimal.h" UCLASS() class UMyUtility : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UFUNCTION( BlueprintCallable, Category="My Utility" ) static FString Experiment( const FString& in ); }
// MyUtility.cpp #include "MyUtility.h" // warning 群だるいので(´・ω:;.:... #ifdef TEXT #undef TEXT #endif #define _APISET_RTLSUPPORT_VER 0 #define _APISET_INTERLOCKED_VER 0 #define _WIN32_WINNT_WINTHRESHOLD 0 #define _APISET_SECURITYBASE_VER 0 #define NTDDI_WIN7SP1 0 #include "mecab.h" FString UMyUtility::Experiment( const FString& in ) { // mecab 準備; (†1) 後述で追記あり static auto tagger = MeCab::createTagger( "" ); if ( ! tagger ) { auto e = MeCab::getLastError(); return FString( "Tagger Error:" ) + FString( e ); } // mecab 的にこれで返ってくる文字列は utf8 auto result = tagger->parse( TCHAR_TO_UTF8( *in ) ); // utf8 -> wchar // note: 今回は <my-project>.Build.cs で Win64 に絞っているので Windows API を使用しています。 const auto size = ::MultiByteToWideChar( CP_UTF8, 0, result, -1, nullptr, 0 ); std::vector< wchar_t > buffer( size + 1 ); ::MultiByteToWideChar( CP_UTF8, 0, result, -1, buffer.data(), buffer.size() ); return FString( buffer.data() ); }
UE4Editor で適当な UMG を作って:
Note: Canvas に Image, Text, Text Box (Multi-Line) です。
input
の On Text Committed
に MyUtility::Experiment
した結果を result
へ出力する Blueprint を書いて:
テストプレイ:
めでたし、めでたし😃
追記1: (†1) mecab
の MeCab::createTagger( "" );
の引数について
ここで渡す "mecab のコマンドライン引数" をこの例のように空文字列にした場合は、 mecab
をビルド時にカスタマイズしていない場合は "C:\Program Files (x86)\MeCab\etc\mecabrc" から mecab
の設定を読み込み、その設定から辞書を読み込む動作となります。
mecabrc
や辞書を別の場所に用意したい場合には、コマンドライン引数の解説 https://taku910.github.io/mecab/mecab.html を参考に次のように引数として mecab
のコマンドライン引数を渡します:
// mecab のコマンドライン引数をまとめて1つの文字列で渡す static auto tagger = MeCab::createTagger( "-r C:\\mecab\\etc\\mecabrc" );
なお、このように mecab
のコマンドライン引数を渡したい場合に、パス文字列に空白文字が入っている場合などは、 mecab
のコマンドライン引数パーサー ( src/param.cpp
の open
関数を参照 ) があまり器用にパースしてくれずに単一の文字列で与えるパターンではエスケープしようが失敗します。そのような場合には "いわゆる argc, argv パターン" でコマンドライン引数を分離済みの形態で与えれば期待動作します。
// mecabrc のパスが "me cab" のように空白文字を含んでいるパターン std::array< char*, 3 > arguments = { "", "-r", "C:\\me cab\\etc\\mecabrc" }; static auto tagger = MeCab::createTagger( arguments.size(), arguments.data() );
追記2: mecabrc の必要最低限の記述と辞書の配置方法
mecabrc: 適当な場所にテキストファイルを作ればよい
; セミコロン始まりがコメント行 ; 最低限、辞書のディレクトリーのパスを記述しておけばよい。 ; $(rcpath) 変数はこの mecabrc のパスに置き換えて処理される。 dicdir = $(rcpath)\..\dic\ipadic
辞書は公式のダウンロード http://taku910.github.io/mecab/#download から「IPA 辞書」を回収してきて mecabrc で定義したディレクトリーなり、 MeCab::createTagger
にコマンドライン引数で直接渡すなりするディレクトリーへ放り込めばよい。
このごちゃごちゃたくさんファイルが入っているディレクトリーを dicdir へ配置する。一般的な配置パスは mecabrc と併せて次のよう:
<somewhere> |- mecab |- etc | |- mecabrc |- dic |-ipadic | |- ... .csv ほか配布の辞書のディレクトリーの中身のたくさんのファイル |-<something the other dic 1> |-<something the other dic 2> |-<something the other dic 3> |- ...
もし例としてではなく、実際に mecab を UE4 プロジェクトで使用したい場合には、 mecabrc や dic は FPaths::ProjectDir()
から適当な相対パスを設定し、配布パッケージに含めればよいでしょう。 ThirdParty ライブラリーを組み入れる例として試すだけであればどこか適当な場所に配置してテストプレイできればよいでしょう。
// mecab は Windows 環境では / 区切りのパスは扱えないのでひと手間追加する // (微妙に混ざっていても扱える事はあるけれど) cosnt auto mecabrc = FPaths::ConvertRelativePathToFull( FPaths::ProjectDir() / TEXT( "mecab/etc/mecabrc" ) ) #ifdef _WIN32 .Replace( TEXT( "/" ), TEXT( "\\" ) ) #endif ; std::array< char*, 3 > arguments = { "", "-r", TCHAR_TO_UTF8( *mecabrc ) }; static auto tagger = MeCab::createTagger( arguments.size(), arguments.data() );