C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

UE4: バイナリー配布や実行時リンク向けのライブラリーを UE4 プロジェクトへ組み入れる方法を mecab で解説

概要

UE4 のプロジェクトの C++ コードへ UE4 とは特に関係の無い一般のライブラリーをリンクするのは少々面倒な事がある。ヘッダーオンリーなライブラリー、例えば Eigenstb あるいは Boost の一部をリンクするような場合は困らない。しかし、バイナリー配布や実行時リンク向けのライブラリー、例えば OpenCVpcl などを組み入れたい場合には少々面倒。

この記事では UE4 には組み込まれていない、比較的小さくビルドも簡単で、 UE4 プロジェクトへの組み入れを試すのに手頃な mecab を例に、具体的な手法を整理する。

Note: mecab は日本語の形態素解析機能を提供するライブラリーですが、形態素解析については本記事の主題ではないので解説しません。

解説の範囲

f:id:USAGI-WRP:20180314081840p:plain

mecab のバイナリーライブラリーのビルドから↑こんなのが UE4 でテストプレイできるまで。

方法

大雑把な手順:

  1. mecab のソースをダウンロード
  2. mecab をビルド
  3. 組み入れるUE4 プロジェクト を作成(既にあるプロジェクトでも可)
  4. UE4 プロジェクトへ mecab のファイルを配置
  5. UE4 プロジェクトの <project-name>.Build.cs をいじって mecab を扱えるように細工する

mecab のビルドまで

ソースの入手:

ビルド:

  1. cd src
  2. fix: Makefile.msvc.in
    1. X86 -> X64
    2. @DIC_VERSION@ -> 102
    3. @VERSION@ -> 0.996
  3. fix: feature_index.cpp
    1. case 't': os_ << (size_t)path -> case 't': os_ << (unsigned int)path
  4. fix: writer.cpp
    1. case 'L': *os << latice->size() -> case 'L': *os << (unsigned int)latice->size()
  5. 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> 直下です。 SourcesPlugins の下ではありません。

<my-project-dir>
  |- ThirdParty
    |- mecab
      |- Includes
      |- Libraries

mecab のファイル群を配置します。

  • Includes: mecabsrc.h ファイル群を全て放り込む
  • Libraries: libmecab.liblibmecab.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 を作って:

f:id:USAGI-WRP:20180314081002p:plain

f:id:USAGI-WRP:20180314081437p:plain

Note: Canvas に Image, Text, Text Box (Multi-Line) です。

inputOn Text CommittedMyUtility::Experiment した結果を result へ出力する Blueprint を書いて:

f:id:USAGI-WRP:20180314081642p:plain

テストプレイ:

f:id:USAGI-WRP:20180314081840p:plain

めでたし、めでたし😃

追記1: (†1) mecabMeCab::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.cppopen 関数を参照 ) があまり器用にパースしてくれずに単一の文字列で与えるパターンではエスケープしようが失敗します。そのような場合には "いわゆる 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コマンドライン引数で直接渡すなりするディレクトリーへ放り込めばよい。

f:id:USAGI-WRP:20180314103947p:plain

このごちゃごちゃたくさんファイルが入っているディレクトリーを dicdir へ配置する。一般的な配置パスは mecabrc と併せて次のよう:

<somewhere>
  |- mecab
      |- etc
      |  |- mecabrc
      |- dic
          |-ipadic
          |   |- ... .csv ほか配布の辞書のディレクトリーの中身のたくさんのファイル
          |-<something the other dic 1>
          |-<something the other dic 2>
          |-<something the other dic 3>
          |- ...

もし例としてではなく、実際に mecabUE4 プロジェクトで使用したい場合には、 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() );