Migemo を使ってローマ字入力で検索できるようにする [C++Builder]

C++Builder で作るアプリに C/Migemo を使ってローマ字入力のまま検索する機能を実装する方法です。サンプルとして Migemo を使った簡易な grep コマンドを作ってみます。

窓替えが最新の migemo.dll と dict で動作しなくなっていたので、原因の調査を兼ねて Migemo の実装方法を整理してみました。

実行イメージ

まず、このページに掲載しているサンプルを実装したときの実行イメージを見ていきます。

Migemo を使った検索

『Migemo』はローマ字入力のまま日本語が検索できるツールです。「syutoku」とローマ字入力して「取得」という日本語をヒットさせることが出来ます。

例えば、Migemo を使った grep コマンドであれば次のような入力と結果になります。

通常の grep では「grep 取得 Migemo.cpp」のように検索したい文字を日本語で指定する必要がありますが、Migemo を使えば上図のように「syutoku」とローマ字で指定して「取得」を含む行を表示させることが出来ます。

『Migemo』についてより詳しい情報が必要な場合は以下のサイトを参照ください。
Migemo: ローマ字のまま日本語をインクリメンタル検索
『migemo』という便利なツールをオススメしたい #migemo – Qiita

migegrep のダウンロード

実行イメージで例にしている migegrep.exe ファイルを置いておきます。実際に動作させてみたい場合にダウンロードしてみてください。

実装

ここから実装です。実行イメージで示した『migegrep.exe』のコードです。

必要な DLL

  • KaoriyYa さんの C/Migemo ページ で公開されている migemo.dll を使います。
  • 正規表現の検索には C++Builder に含まれる TRegEx::Matches() を使います。他の dll は使いません。

migemo の実装方法をネットで調べると begexp.dll や bregonig.dll を使う方法が出てきますが、複雑でない正規表現パターンであれば TRegEx で出来るようになっていますので、このページでは TregEx を使っています。

ファイル構成

次のようなファイル構成で作ります。

migegrep/
|- cmigemo-1.3/
| `- migemo.h // KaoriYa さんの GitHub からダウンロードする
|- Migemo.h // このページに記載したコード
|- Migemo.cpp // このページに記載したコード
`- Main.cpp // このページに記載したコード

コード

migemo.h

migemo.h は、KaoriYa さんの GitHub からダウンロードします。
こちら → https://github.com/koron/cmigemo/blob/master/src/migemo.h
ダウンロードしたファイルは、前述のソース構成どおり cmigemo-1.3/migemo.h に配置してください。

Migemo.h, Migemo.cpp

Migemo クラスは、C/Migemo を使った検索を簡易化するために作成したクラスです。migemo.dll のロードや検索をまとめています。今回の例以外でもコピーしてそのまま使えると思います。エラー処理は printf() になっていますので、エラー処理だけは自分の実装に合わせて変更してください。

#ifndef  _MIGEMO_H_
#define  _MIGEMO_H_

#include "cmigemo-1.3/migemo.h"

/**
 * migemo.dll に含まれる関数の型。
 * MIGEMO_CALLTYPE は、migemo.h で定義されているマクロ。
 */ 
typedef migemo* MIGEMO_CALLTYPE(*migemo_open_t)(char*);
typedef void MIGEMO_CALLTYPE (*migemo_close_t)(migemo*);
typedef unsigned char* MIGEMO_CALLTYPE (*migemo_query_t)(migemo*, const unsigned char*);
typedef void MIGEMO_CALLTYPE (*migemo_release_t)(migemo*,const unsigned char*);
typedef int MIGEMO_CALLTYPE (*migemo_load_t)(migemo*,int,const char*);
typedef int MIGEMO_CALLTYPE	(*migemo_is_enable_t)(migemo*);

/**
 * class Migemo
 */
class Migemo {
   public:
    Migemo(const UnicodeString &i_migemoDllDirPath,
           const UnicodeString &i_migemoDictDirPath);
    ~Migemo();
    bool IsEnabled();
    TMatchCollection Match(const UnicodeString &i_query,
                           const UnicodeString &i_targetString);

   private:
    // migemo を使用するのに必要な dll や 辞書ファイルのパス
    UnicodeString m_migemoDllPath;
    UnicodeString m_migemoDictPath;

    // migemo.dll に含まれる関数
    migemo_open_t migemo_open;
    migemo_close_t mogemo_close;
    migemo_query_t migemo_query;
    migemo_release_t migemo_release;
    migemo_load_t migemo_load;
    migemo_is_enable_t migemo_is_enable;

    // migemo.dll のインスタンスハンドル
    HINSTANCE m_hInstMigemo;

    // migemo_open() で取得する migemo オブジェクト
    migemo *m_migemop;

    void _SetMigemoFilePath(const UnicodeString &i_migemoDllDirPath,
                            const UnicodeString &i_migemoDictDirPath);
    void _LoadMigemo();
    void _OpenMigemo();
    void _CloseMigemo();
    void _UnloadMigemo();
    UnicodeString _QueryMigemo(const UnicodeString &i_query);
};

/*
   使用例
   -----------------------------------------------------------------------------------------------
   // テストデータ
   UnicodeString queryString = L"syutoku";
   UnicodeString targetString = L" * @brief 「migemo が利用可能か」を取得する。";

   // Migemo オブジェクトを生成する。
   // 引数は exe と同じフォルダに migemo.dll と dict フォルダを置いた時の指定。
   Migemo* cMigemop = new Migemo(L".", L"./dict");
   if (cMigemop == NULL || (cMigemop && cMigemop->IsEnabled() == false)) {
       // エラー処理
   }

   // 検索を実行する。
   TMatchCollection matches =     // 一致した日本語文字列と位置のコレクションが返る
       cMigemop->Match(
           queryString,           // 検索したい半角英字の文字列(入力したローマ字の文字列)
           targetString);         // 検索対象の文字列

   // 結果をコンソールに表示する。
   for (auto match : matches) {
       // "取得(27,2)" が表示される。 targetString の 27文字からの2文字「取得」がヒットしている。
       printf("%s(%d,%d)\n", AnsiString(match.Value).c_str(), match.Index, match.Length);
   }
   -----------------------------------------------------------------------------------------------
 */

#endif /* _MIGEMO_H_ */
#include <RegularExpressions.hpp> // use TRegexp
#include "Migemo.h"

/**
 * @brief コンストラクタ
 * @param i_migemoDllDirPath  migemo.dll  のあるディレクトリを指定する。
 * @param i_migemoDictDirPath migemo-dict のあるディレクトリを指定する。
 */
Migemo::Migemo(const UnicodeString &i_migemoDllDirPath,
               const UnicodeString &i_migemoDictDirPath)
    : m_hInstMigemo(NULL), m_migemop(NULL)
{
    // migemo を使用するのに必要な dll や 辞書ファイルのパスを構築する。
    _SetMigemoFilePath(i_migemoDllDirPath, i_migemoDictDirPath);

    // migemo.dll をロードし、含まれている関数のアドレスを取得する。
    _LoadMigemo();
    if (m_hInstMigemo == NULL) return;

    // migemo をオープンして migemo を利用可能な状態にする。
    _OpenMigemo();
}

void Migemo::_SetMigemoFilePath(const UnicodeString &i_migemoDllDirPath,
                                const UnicodeString &i_migemoDictDirPath)
{
    // migemo-dict は utf-8 の辞書を使用する。プログラム内の文字列は utf-8 で扱う。
    m_migemoDllPath = IncludeTrailingPathDelimiter(i_migemoDllDirPath) + "migemo.dll";
    m_migemoDictPath = IncludeTrailingPathDelimiter(i_migemoDictDirPath) + "\\utf-8\\migemo-dict";
}

void Migemo::_LoadMigemo()
{
    // migemo.dll をロードする。
    m_hInstMigemo = LoadLibrary(m_migemoDllPath.c_str());
    if (m_hInstMigemo == NULL) {
        DWORD le = GetLastError();
        printf("migemo.dll のロードに失敗しました。指定されたパス:\"%s\" GetLastError=%d", 
            AnsiString(m_migemoDllPath).c_str(), le);
        return;
    }

    // migemo.dll に含まれている関数のアドレスを取得する。
    migemo_open = (migemo_open_t)GetProcAddress(m_hInstMigemo,"migemo_open");
    mogemo_close = (migemo_close_t)GetProcAddress(m_hInstMigemo,"migemo_close");
    migemo_query = (migemo_query_t)GetProcAddress(m_hInstMigemo,"migemo_query");
    migemo_release = (migemo_release_t)GetProcAddress(m_hInstMigemo,"migemo_release");
    migemo_load = (migemo_load_t)GetProcAddress(m_hInstMigemo,"migemo_load");
    migemo_is_enable = (migemo_is_enable_t)GetProcAddress(m_hInstMigemo,"migemo_is_enable");
    if ((migemo_open && mogemo_close && migemo_query && migemo_release && migemo_load && migemo_is_enable) == false) {
        printf(
            "関数が読み込めません。"
            "migemo.dll のバージョンを確認してください。");
        FreeLibrary(m_hInstMigemo);
        m_hInstMigemo = NULL;
        return;
    }
    return;
}

void Migemo::_OpenMigemo()
{
    m_migemop = migemo_open(AnsiString(m_migemoDictPath).c_str());
}

/**
 * @brief デストラクタ
 */
Migemo::~Migemo()
{
    _CloseMigemo();
    _UnloadMigemo();
}

void Migemo::_CloseMigemo()
{
    if (m_migemop == NULL) return;
    mogemo_close(m_migemop);
    m_migemop = NULL;
}

void Migemo::_UnloadMigemo()
{
    if (m_hInstMigemo == NULL) return;
    FreeLibrary(m_hInstMigemo);
    m_hInstMigemo = NULL;
}

/**
 * @brief 「migemo が利用可能か」を取得する。
 * @return migemo が利用できる(true)、migemo が利用できない(false)が返ります。
 */
bool Migemo::IsEnabled() { return m_hInstMigemo && m_migemop && migemo_is_enable(m_migemop); }

/**
 * @brief 対象の文字列に 半角英字で入力した文字列が含まれるかを判定し、含まれていれば
 *        一致した日本語文字列と位置のコレクションを返します。
 * @param i_query 検索する半角英字の文字列(入力したローマ字の文字列)
 * @param i_targetString 検索対象の文字列
 * @return 一致した日本語文字列と位置のコレクションが返ります。
 * 使用例
 * for (auto match : matches) {
 *     printf("%s(%d,%d)\n", AnsiString(match.Value).c_str(), match.Index, match.Length);
 * }
 * @note return の TMatchCollection を受け取るには
 * #include <RegularExpressions.hpp> が必要です。
 *
 */
TMatchCollection Migemo::Match(const UnicodeString &i_query,
                               const UnicodeString &i_targetString)
{
    UnicodeString pattern = _QueryMigemo(i_query);
    return TRegEx::Matches(i_targetString, pattern);
}

UnicodeString Migemo::_QueryMigemo(const UnicodeString &i_query)
{
    // migemo から i_query に一致する正規表現パターンを取得する。
    // i_query を char* にするために AnsiString に入れて c_str() する。
    unsigned char *patternChars =
        migemo_query(m_migemop, (unsigned char *)AnsiString(i_query).c_str());
    if (patternChars == NULL) return L""; // 対象なし

    // patternChars に入った正規表現パターン文字列を返す。
    // UTF8 の辞書を使っているので patternChars は UTF8 で返っているが、char*
    // から変換するには SJIS を経由する。
    UnicodeString pattern = Utf8ToAnsi((char *)patternChars);  // UTF8 <- SJIS <- UTF8

    migemo_release(m_migemop, patternChars);
    return pattern;
}

Main.cpp

コマンドライン引数の一つ目に「検索したいローマ字入力の文字列」、二つ目に「検索対象のファイル名」を指定して、Migemo を使った grep を実行するコードです。

Migemo 関連の処理は 21~24行目、39~40行目です。
21 行目:前述の Migemo.h,cpp の Migemo オブジェクトを生成しています。引数には migemo.dll の置いたフォルダパスと migemo-dict を含むフォルダのパスを指定します。
22~24行目:CMigemop->IsEnabled() で、利用可能かどうかを判定しています。
39~40行目:CMigemop->Match() で、コマンドラインの第一引数(queryString)のローマ字の文字列がファイルから読んだ行(targetString) に含まれているか Migemo を使って検索しています。matches.Count には一致した個数が入ります。

#include <tchar.h>
#include <vcl.h>
#include <RegularExpressions.hpp> // Migemo::Match() の返値に使用する

#include <iostream>
#include <fstream>
#include "Migemo.h"

/**
 * main
 */
int _tmain(int argc, _TCHAR* argv[])
{
    UnicodeString migemoDllDirPath = L".";
    UnicodeString migemoDictDirPath = L".\\dict";

    UnicodeString queryString = argv[1]; // 検索したい半角英字の文字列(ローマ字入力の文字列)
    AnsiString inputFile = argv[2];  // 検索対象のファイル名

    // Migemo オブジェクトを生成する。
    Migemo* cMigemop = new Migemo(migemoDllDirPath, migemoDictDirPath);
    if (cMigemop == NULL || (cMigemop && cMigemop->IsEnabled() == false)) {
        return -1;
    }

    // 検索を実行する。
    // cin の入力元をファイルに変更して検索する。
    std::ifstream isf((const char*)inputFile.c_str());
    std::cin.rdbuf(isf.rdbuf());

    // ファイルを読んで、指定した半角英字が一致するか検索する。
    int numFound = 0;
    std::string readLine;
    while (getline(std::cin, readLine)) {
        // cin から読んだ行を UTF8 で格納する。
        UnicodeString targetString = Utf8ToAnsi(readLine.c_str());  // UTF8 <- SJIS <- UTF8

        // 入力した半角英字に一致するか判定し、一致した日本語文字列のコレクションを取得する。
        TMatchCollection matches = cMigemop->Match(queryString, targetString);
        if (matches.Count == 0) continue;

        // 検索対象の文字列、入力文字、一致した文字 をコンソールに出力する。
        printf("%s", AnsiString(targetString).c_str());

        // 以下を有効にすると行末に 一致した日本語文字列と位置が表示される。
        //printf("| %s(%d,%d)", AnsiString(match.Value).c_str(), match.Index, match.Length);

        printf("\n");
        numFound++;
    }
    if (numFound == 0) {
        printf("一致する文字列がありません。\n");
    }

    return 0;
}

なお、読み込むファイルは UTF-8 のみサポートしています。SJISを読みたい場合は、36行目を以下のように変更すると良いでしょう。

-       UnicodeString targetString = Utf8ToAnsi(readLine.c_str());  // UTF8 <- SJIS <- UTF8
+       UnicodeString targetString = readLine.c_str();  // UTF8 <- SJIS

実行

上記がビルドできても migemo.dll が無いと動きません。入手して配置します。

migemo.dll と dict フォルダを配置

KaoriyYa さんの C/Migemo ページ から「C/Migemo for Windows 64bit」をダウンロードしてください。

ダウンロードした『cmigemo-default-win64-20110227.zip』(2025/03/17時点) を展開して、含まれている migemo.dll と dict フォルダを migegrep.exe と同じフォルダに配置してください。

migegrep/
|- cmigemo-1.3/
| `- migemo.h
|- Migemo.h
|- Migemo.cpp
|- Main.cpp
`- Win64x/
`- Debug/
|- dict/

| `- utf-8/
| |- han2zen.dat
| |- hira2kata.dat
| |- migemo-dict
| |- roma2hira.dat
| `- zen2han.dat
|- migemo.dll
`- migegrep.exe

実行する

コンソールを起動します。

.\migegrep.exe syutoku ../../Migemo.cpp を実行してみます。

Migemo.cpp から「取得」の文字が含まれる行が表示できました。

参考

コメント