last updated:2021/08/02

Hidemaru Q and A

第III部〜秀丸マクロのいろはにほへと
 HMJRE.DLLを使う場合の注意点


HMJRE.DLLを使う場合の注意点

はじめに

秀丸エディタの正規表現エンジンであるHMJRE.DLLは、マクロから呼び出すことが可能です。 (マクロからのdllfunc呼び出し)

文字変数や文字列に対して、正規表現を使った検索、置換が実行できますが、使用する場合には注意点があります。

秀丸エディタ本体で検索する場合は秀丸エディタがつじつまを合わせてくれますが、マクロで使う場合は呼び出した側が注意しないといけない場合があります。

文字変数にShift_JISに含まれない、Unicode文字がある場合

Unicode文字が含まれる場合の問題点

秀丸エディタの内部的な事情を少し説明します。

基本的にはShift_JISである前提で動作しています。

通常、外部DLL内の関数を dllfunc/dllfuncstr を使って呼び出す時は、引数で指定する文字列に「Shift_JISに含まれないUnicode文字」が含まれると、その文字を削除して外部DLLに渡されます。

これは DLLとのインタフェースが、マルチバイト文字(Shift_JIS)を前提としている事と、「特殊な文字コード」を外部DLLが理解できないためです。 (dllfuncw/dllfuncstrw を使えば、Unicode文字列を外部DLLに渡す事は可能ですが、DLL側の対応が必要です)

HMJRE.DLLは、例外的に「 特殊な文字コード」 を理解できる為、 dllfunc/dllfuncstr 呼び出し時、文字列内に「Shift_JISに含まれないUnicode文字」があっても、秀丸エディタ内部の「特殊な文字コード」がそのままHMJRE.DLLに渡されます。

HMJRE.DLLの関数自体は、マッチ位置やマッチした長さを、バイト数で返します。「特殊な文字コード」は「1文字4バイト」なので、「常に漢字は2バイト」で扱うマクロ関数と一致しません。

以下のマクロは、文字変数 $a の最後にある"a"の位置を返すマクロです。マクロ関数とHMJRE.DLL内の関数の比較になります。

// マクロ1
// このマクロはUnicode(UTF-16LE)で保存する事
//    0         1
//    01234567890
$a = "hidemaru𠀋a";
#n1 = strrstr($a, "a");
// 見つけた場所から1文字切り出す
$b1 = midstr($a, #n1, 1);

loaddll "HMJRE.DLL";
if(!result){
    message "DLLのロードに失敗しました。";
    endmacro;
}
#n2 = dllfunc("FindRegular", "a$", $a, 0);
if(0 > #n2){
    message "検索に失敗しました。";
    endmacro;
}
#l2 = dllfunc("GetLastMatchLength");
// マッチした箇所を切り出す
$b2  = midstr($a, #n2, #l2);
message "場所(#n1)     :" + str(#n1) + "\n" +
        "場所(#n2)     :" + str(#n2) + "\n" +
        "取得文字1($b1):" + $b1 + "<--" + "\n" +
        "取得文字2($b2):" + $b2 + "<--";
freedll;
endmacro;

「マクロでは、常に漢字は2バイトとして扱われる」 ので、最後の"a"は10を期待します。

//              ★
//    0         1
//    01234567890
$a = "hidemaru𠀋a";

しかし、マクロを実行した結果は以下となり、strrstr関数は期待通り「10」を返しますが、FindRegular関数は「16」を返します。

場所(#n1)     :10
場所(#n2)     :16
取得文字1($b1):a<--
取得文字2($b2):<--

これは "𠀋"という文字が、UTF-16で表現するとサロゲートペアとなるため、Unicode文字2文字で表現されているからです。「特殊な文字コード」は1文字4バイトなので、2文字分で8バイトになっています。

先ほどのマクロを変更して、マッチした長さを返すGetLastMatchLength関数を使用し、 "𠀋"を検索した時のマッチした長さ(バイト数)を確認してみます。

// マクロ2
// このマクロはUnicode(UTF-16LE)で保存する事
//    0         1
//    01234567890
$a = "𠀋123456789";
#l1  = strlen($a);

loaddll "HMJRE.DLL";
if(!result){
    message "DLLのロードに失敗しました。";
    endmacro;
}
#n2 = dllfunc("FindRegular", "𠀋", $a, 0);
if(0 > #n2){
    message "検索に失敗しました。";
    endmacro;
}
#l2 = dllfunc("GetLastMatchLength");
// マッチした箇所を切り出す
$b2  = midstr($a, #n2, #l2);
message "文字列長(#l1):" + str(#l1) + "\n" +
        "場所(#n2)    :" + str(#n2) + "\n" +
        "長さ(#l2)    :" + str(#l2) + "\n" +
        "取得文字($b2):" + $b2 + "<--";
freedll;
endmacro;

マクロ2の実行結果です。

文字列長(#l1):11
場所(#n2)    :0
長さ(#l2)    :8
取得文字($b2):𠀋123456<--

"長さ(#l2)"が、"𠀋" を検索しマッチしたバイト数になり、8である事が確認できます。

以上のことから、マクロ1実行時にDLLへ渡される $a の中身は以下の様なデータになっている為、 "FindRegular"の場合、最後の"a"の位置は 「16」 となります。 ("𠀋"は、"XXXX"と"YYYY" の様な、8バイトで表現される)

//    0         1         2
//    012345678901234567890
$a = "hidemaruXXXXYYYYa";

マクロでは、常に漢字は2バイトとして扱われる」ということになっていますが、 HMJRE.DLLの関数使用時に「特殊な文字コード」が含まれると、漢字1文字でも最大で8バイトになる事もあるため、マクロ関数と一致しません。マクロ1、マクロ2で、"取得文字($b2)"がおかしいのは、この不一致が原因です。

HMJRE.DLL内の関数で戻り値をマクロの関数で使用する場合は、ズレの有無をチェックし補正しないと、正確な位置・長さを取得出来ません。

対処法

HMJRE.DLL Ver.3.13(秀丸エディタ Ver.8に添付)から、"SetUnicodeIndexAutoConvert関数"が追加されています。

//#flg = 0 自動変換無効、#flg = 1 自動変換有効
#n = dllfunc("SetUnicodeIndexAutoConvert", #flg);

この関数を使用すると、HMJRE.DLLの各関数の戻り値が、マクロでそのまま使用できるような値に自動変換されます。

"SetUnicodeIndexAutoConvert関数" を使い自動変換を有効にしたマクロです。

//マクロ3
// このマクロはUnicode(UTF-16LE)で保存する事
//    0         1
//    01234567890
$a  = "hidemaru𠀋a";
// $a に入っている長さ(バイト数)
#l  = strlen($a);
// $a の最後から、"a" を探す
#n1 = strrstr($a, "a");
// 見つけた場所から1文字切り出す
$b1 = midstr($a, #n1, 1);

loaddll "HMJRE.DLL";
if(!result){
    message "DLLのロードに失敗しました。";
    endmacro;
}
// 引数に 1 を設定し、自動変換を有効にする(0 で無効化)
#x = dllfunc("SetUnicodeIndexAutoConvert", 1);
// $a から最後にある "a" の位置を取得
#n2 = dllfunc("FindRegular", "a$", $a, 0);
if(0 > #n2){
    message "検索に失敗しました。";
    endmacro;
}
// マッチした内容の長さを取得
#l2 = dllfunc("GetLastMatchLength");
// マッチした箇所を切り出す
$b2  = midstr($a, #n2, #l2);
//結果表示
message "文字列長(#l) :" + str(#l) + "\n" +
        "場所    (#n1):" + str(#n1) + "\n" +
        "場所    (#n2):" + str(#n2) + "\n" +
        "長さ    (#l2):" + str(#l2) + "\n" +
        "取得文字($b1):" + $b1 + "<--" + "\n" +
        "取得文字($b2):" + $b2 + "<--";
freedll;
endmacro;

上記マクロの実行結果です。

文字列長(#l) :11
場所    (#n1):10
場所    (#n2):10
長さ    (#l2):1
取得文字($b1):a<--
取得文字($b2):a<--

"FindRegular"の戻り値を、そのまま使用しても、正常に取得できています。 ("SetUnicodeIndexAutoConvert" の自動変換が有効になっている)

ということで、マクロからHMJRE.DLLを使う場合は以下の手順になります。

  1. SetUnicodeIndexAutoConvert関数を、引数に「1」を指定して呼び出し、自動変換を有効にする。
  2. 各関数を呼び出す。

確実に「Shift_JISに含まれないUnicode文字」がないと判っているなら不要ですが、 HMJRE.DLLをマクロから使用する場合には、SetUnicodeIndexAutoConvert関数 を実行することを推奨します。

外国語を対象とする場合

マクロでは、基本的に日本語(Shift_JIS)を対象にしているため、日本語以外の言語使用する場合、検索に失敗する場合があります。

Shift_JISの文字コードに関係する話です。

Shift_JISの文字コードの割り当ては以下のようになっています。

0x00〜0x1f、0x7f
この範囲は制御コードが割り当てられている。
0x20〜0x7e
ASCII文字が割り当てられている。ASCII文字とは英数字と記号になります。
0xa1〜0xdf
半角カタカナです。
0x81〜0x9f、0xe0〜0xef
Shift_JISの上位バイト。
0x40〜0x7e、0x80〜0xfc
Shift_JISの下位バイト。

欧文文字コード(CP1252)の場合、Shift_JISの上位バイトの範囲に文字が割り当てられています。 (0xe0〜0xef の 範囲は、アクセント記号付きの文字が割り当てられている)
その為、欧文文字を検索したくても Shift_JIS の上位バイトと認識され、正しく検索出来ない場合があります。日本語以外の言語を使用する場合には、あらかじめ言語の情報をDLLに教えておく必要があります。

HMJRE.DLLのVer.5.01から、"NotifyEncode"関数が追加されています。 (秀丸エディタのVer.8.74以降から添付)
この関数を使用することで、対象のエンコードを指定できます。

//ファイルと同じ文字コードを使う場合には、引数に "encode"キーワードを指定する。
//ファイルと違う文字コードを使う場合には、"encode"キーワードの値を調べて指定する。
#n = dllfunc("NotifyEncode", encode);

マクロから外国語を対象にして HMJRE.DLL を使う場合には、以下の手順になります。

  1. NotifyEncode関数に、対象のエンコード値を指定して呼び出す。
  2. 各関数を呼び出す。

目次に戻る