秀丸終了時に DLL を ...No.10988
杉浦 まさき さん 02/01/16 23:39
 
こちらではお久しぶりです。
杉浦 まさき です。

え〜と、先日β公開した BREGEXP DLL の秀丸マクロ用ラッパー DLLを
マクロで使用して、マクロ内で freedll せずにマクロを終了させ、
その秀丸ウィンドウを閉じたときに保護違反で落ちてしまいました。
#Ver.3.14β5で確認

デバッガで動作を追ってみると、どうも以下のような事態になって
いるようです(?)。

[マクロ中]
  loaddll "BRE_Wrap.dll";  // LoadLibrary("BRE_Wrap.dll");

[BRE_Wrap.dll 内]
  DllMain() 内の DLL_PROCESS_ATTACH
    hModule = LoadLibrary("BREGEXP.DLL");
  DllMain() 内の DLL_PROCESS_DETACH
    // BREGEXP.DLL が返したメモリを開放←ここで落ちる
    FreeLibrary(hModule);

この時点でDLLがロードされた順番がOSによって記憶されており、
秀丸終了時に明示的に FreeLibrary() されていない DLL は
OSによって「ロードされた順序とは逆の順序で」アンロードされる。
#↑これがイマイチ確信がないです(^^;。

で、アンロードは BREGEXP.DLL → BRE_Wrap.dll の順に実行され、
BRE_Wrap.dll の FreeLibrary() のところでアクセス違反で落ちる…
と、多分(^^;こういうことじゃないかと思っています。
#マクロ中で freedll すれば落ちないので。
この予想が正しいかどうかは??ですが、もし秀丸ウィンドウの
クローズのときにマクロでロードした DLL を明示的にアンロード
していない場合は、面倒でも FreeLibrary() でアンロードして
もらえると嬉しいです。

予想が外れていたらすみませんm(_ _)m。
#この場合何で落ちるのかが謎ですが…。

BRE_Wrap.DLL の URL:http://www.ceres.dti.ne.jp/~sugiura/hidemaru/macros/bre_
wrapper/bre_wrap_000b.lzh


[ ]
RE:10988 秀丸終了時に DLL を ...No.10998
ENCODINGSHIFTJIS さん 02/01/17 16:53
 
以下の場合では保護違反にはなりませんでした。
秀丸正規表現DLL を Baba にして、loaddll のみの
 loaddll "BRE_Wrap.dll";
マクロを実行した後、閉じた場合。

[ ]
RE:10988 秀丸終了時に DLL を ...No.11000
秀丸担当 さん 02/01/17 19:27
 
>この予想が正しいかどうかは??ですが、もし秀丸ウィンドウの
>クローズのときにマクロでロードした DLL を明示的にアンロード
>していない場合は、面倒でも FreeLibrary() でアンロードして
>もらえると嬉しいです。

私のところでも再現できませんでしたが、明示的にアンロードする
のは問題無いと思うので、次のβ版でしておこうと思います。

[ ]
RE:11000 秀丸終了時に DLL を ...No.11007
杉浦 まさき さん 02/01/18 00:40
 
こんばんは。
杉浦 まさき です。

>私のところでも再現できませんでしたが、明示的にアンロードする
>のは問題無いと思うので、次のβ版でしておこうと思います。

ENCODINGSHIFTJIS さんの例のようにロードだけした場合は
BREGEXP DLL からメモリを渡されることがないので
エラーにならないです。<最初に書いとけばよかったですm(_ _)m
#あと WinME だと落ちないとの報告もあるんですが…
 DLL の自動アンロードは OS 依存ってことなんでしょうかねぇ…??

で、こちら側でも一応落ちないように対策はしたんですが、
やはり明示的にアンロードしていただけるとありがたいですm(_ _)m。

[ ]
RE:11007 秀丸終了時に DLL を ...No.11009
さん 02/01/18 09:10
 
こんにちは、小西です。

>ENCODINGSHIFTJIS さんの例のようにロードだけした場合は
>BREGEXP DLL からメモリを渡されることがないので
>エラーにならないです。<最初に書いとけばよかったですm(_ _)m
>#あと WinME だと落ちないとの報告もあるんですが…
> DLL の自動アンロードは OS 依存ってことなんでしょうかねぇ…??

ということは、ヒープの共有違反あたりのバグがあるのでは?
どっちがどっちのヒープを使っているのかわかりませんが、相手のヒープを使って、
勝手にそのヒープを殺したりすると、そりゃ一般保護違反になります。

FreeLibraryはOSが勝手にやってくれるので明示的にやったほうが気持ちいいですが、
32bitでは、関係ないはず。(だよな?)

[ ]
RE:11007 秀丸終了時に DLL を ...No.11010
える さん 02/01/18 10:00
 
>ENCODINGSHIFTJIS さんの例のようにロードだけした場合は
>BREGEXP DLL からメモリを渡されることがないので
>エラーにならないです。<最初に書いとけばよかったですm(_ _)m

ここだけ読むと、BRE_Wrap.dll が、秀丸のヘルプにある

| 文字列を返す関数を作りたい場合には、返り値をchar*型で宣言してください。
| char*型で宣言した関数が返す値は、スタック上ではなく、固定のメモリ領域のア
ドレスでなくてはいけません。

という部分を守っていないだけに見えます。

[ ]
RE:11010 秀丸終了時に DLL を ...No.11020
杉浦 まさき さん 02/01/19 02:09
 
みなさんこんばんは。
杉浦 まさき です。

>ここだけ読むと、BRE_Wrap.dll が、秀丸のヘルプにある
>| 文字列を返す関数を作りたい場合には、返り値をchar*型で宣言してください。
>| char*型で宣言した関数が返す値は、スタック上ではなく、固定のメモリ領域のア
>ドレスでなくてはいけません。
>という部分を守っていないだけに見えます。

それはちゃんと守っています。

状況を詳しく言うと、まず BREGEXP DLL の BMatch() 等の関数が
(正規表現のパターン解釈結果を呼び出し側でキャッシュするために)
BREGEXP DLL 内部で確保したヒープ領域へのポインタを
呼び出し側に返していて、それを開放する DLL 関数を
BRE_Wrap.dll のアンロード時にまとめて呼んでから
BREGEXP DLL を FreeLibrary() しています。

マクロ中で freedll しなかった場合は、秀丸終了時に
OS側で DLL のアンロードが行われるわけですが、この場合
メモリを開放する関数の呼び出しをする前に既に
BREGEXP DLL の返してきたポインタが指している領域が
アクセス不可になってしまっていました。
#↑IsBadReadPtr() が非ゼロを返す

マクロ内で freedll して BRE_Wrap.dll をアンロードした場合は
アクセス違反になることなく FreeLibrary() までできています。
以上のことから、マクロ内で freedll せずに
秀丸終了時に DLL のアンロードをOSに任せた場合、
BRE_Wrap.dll 内でロードした BREGEXP DLL が BRE_Wrap.dll
本体より先にアンロードされている(から BREGEXP DLL が
確保したヒープ領域がアクセス不可の領域になっている)と
推察しています。
#長くなってすんませんm(_ _)m。>ALL

[ ]
RE:11020 秀丸終了時に DLL を ...No.11021
える さん 02/01/19 05:27
 
>BREGEXP DLL 内部で確保したヒープ領域へのポインタを
>呼び出し側に返していて、それを開放する DLL 関数を
>BRE_Wrap.dll のアンロード時にまとめて呼んでから
>BREGEXP DLL を FreeLibrary() しています。
(略)

じゃあ、常に BREGEXP 側の開放関数を呼ばないのではダメですか?

そうすれば、マクロで freelibrary した場合も、OS が
freelibrary した場合も BREGEXP.DLL が開放するので問題がな
さそうですが。

どちらかというと変に隠蔽せずに秀丸のマクロ側に BREGEXP 側で
確保されたメモリ領域を示す値(ポインタなど)をハンドルとして
返してしまって、開放は秀丸マクロを構築している人の責任にする
のがシンプルで良くある実装になるとも思います。

[ ]
RE:11020 秀丸終了時に DLL を ...No.11026
小西 さん 02/01/19 13:30
 
杉浦さん今日は。

>状況を詳しく言うと、まず BREGEXP DLL の BMatch() 等の関数が
>(正規表現のパターン解釈結果を呼び出し側でキャッシュするために)
>BREGEXP DLL 内部で確保したヒープ領域へのポインタを
>呼び出し側に返していて、それを開放する DLL 関数を
>BRE_Wrap.dll のアンロード時にまとめて呼んでから
>BREGEXP DLL を FreeLibrary() しています。

BREGEXP.DLLがアンロードされるときにBRE_WRAP.DLL側でメモリの開放をする必要が
あるのでしょうか? BREGEXP.DLL側はしないのかなぁ?
いずれにしても、プロセス自体が終了するわけだし、Windowsのグローバルなところ
に触ってるとか、アンロードプロセスで永続化の処理が必要でなければ問題ないよう
な気がします。

>以上のことから、マクロ内で freedll せずに
>秀丸終了時に DLL のアンロードをOSに任せた場合、
>BRE_Wrap.dll 内でロードした BREGEXP DLL が BRE_Wrap.dll
>本体より先にアンロードされている(から BREGEXP DLL が
>確保したヒープ領域がアクセス不可の領域になっている)と
>推察しています。
簡単なプログラムを書いて調べてみましたが、ライブラリのロードアンロードはFILO
の原則みたいです。それはそれで納得のいく仕様ですが。
あと、OSによるアンロードはDLL_PROCESS_DETACHイベントのときにlpvReservedにNUL
L以外のパラメータが入ってきます。
この辺を手がかりにBRE_WRAP.DLLの処理を変更してはどうでしょうか?



[ ]
RE:11021 秀丸終了時に DLL を ...No.11029
杉浦 まさき さん 02/01/20 01:10
 
えるさん、こんばんは。
杉浦 まさき です。

>じゃあ、常に BREGEXP 側の開放関数を呼ばないのではダメですか?

まぁそれも一つの手段ではあると思いますが…。
#手元のバージョンはポインタの正当性チェックをしてから
 開放関数を呼ぶように変更済みなので、
 また直すのは単純に面倒です(^^;。


>どちらかというと変に隠蔽せずに秀丸のマクロ側に BREGEXP 側で
>確保されたメモリ領域を示す値(ポインタなど)をハンドルとして
>返してしまって、開放は秀丸マクロを構築している人の責任にする
>のがシンプルで良くある実装になるとも思います。

う〜ん、今回の場合はそれもいいかもしれません。
問題のメモリ領域に入っている正規表現パターンの
キャッシュを使うために、パターン文字列をハッシュテーブルに
記憶して次回の呼び出しのときにキャッシュをすぐ探せるように
しているんですが、同じパターンを渡したかどうかはマクロ作者が
一番よく知っているはずですからねぇ…。

ただ、個人的な好みというか、一マクロ書きとしては、
たかがマクロに(笑)あまり責任を押し付けられてもなぁと
思っているので、よほどの理由でもない限り関数仕様は
なるべく安全側に振っています。
#まぁ秀丸マクロの場合は仮にポインタ値を返しても
 マクロ内では大したことはできないとは思いますが(^^;。


[ ]
RE:11026 秀丸終了時に DLL を ...No.11030
杉浦 まさき さん 02/01/20 01:29
 
小西さん、こんばんは。
杉浦 まさき です。

>BREGEXP.DLLがアンロードされるときにBRE_WRAP.DLL側で
>メモリの開放をする必要があるのでしょうか?
> BREGEXP.DLL側はしないのかなぁ?

専用の開放関数があるのでしていなくても不思議ではないですが…
いずれにしても BREGEXP DLL のソースコードを見ていないので
僕にはわからないです。


>いずれにしても、プロセス自体が終了するわけだし、
>Windowsのグローバルなところに触ってるとか、
>アンロードプロセスで永続化の処理が必要でなければ
>問題ないような気がします。

まったくその通りですが、malloc()(new) を呼んだら
必ず free()(delete) しなければ気が済まない、そんな
ところです(笑)。
#実生活ではズボラが服着て歩いてるようなσ(^^)なんですが、
 この件についてはそういう癖がついてしまいました(^^;。


>簡単なプログラムを書いて調べてみましたが、
>ライブラリのロードアンロードはFILOの原則みたいです。
>それはそれで納得のいく仕様ですが。

わざわざ調べていただいてどうもですm(_ _)m。
確かに仕様的には納得のいくものだと思います。
というわけで、今回の場合のように親プロセスが
FreeLibrary() で自分を解放しなかった場合を
想定してコードを書く必要があるということが
わかったので、自分としてはとても勉強になりました(^^)。


>あと、OSによるアンロードはDLL_PROCESS_DETACHイベントのときに
>lpvReservedにNULL以外のパラメータが入ってきます。
>この辺を手がかりにBRE_WRAP.DLLの処理を変更してはどうでしょうか?

おお!こんな仕様になってたとは気が付きませんでした(^^;。
#"Reserved" に意味を割り当てていたなんて… というか、名前変えてくれればい
いのに(笑)。>M$DN


[ ]
RE:11030 秀丸終了時に DLL を ...No.11033
える さん 02/01/20 08:14
 
>まったくその通りですが、malloc()(new) を呼んだら
>必ず free()(delete) しなければ気が済まない、そんな
>ところです(笑)。

このレベルの問題性を指標とするならば、完全に既知で
ない DLL を DllMain() の中から LoadLibray() を呼
び出す時点でマズいです。

BREGEXP.DLL であれば、たぶん問題ないでしょうけど。


[ ]
RE:11033 秀丸終了時に DLL を ...No.11038
杉浦 まさき さん 02/01/20 16:43
 
えるさん、こんにちは。
杉浦 まさき です。

#秀丸にあまり関係ない話で申し訳ないですm(_ _)m。>ALL

>>まったくその通りですが、malloc()(new) を呼んだら
>>必ず free()(delete) しなければ気が済まない、そんな
>>ところです(笑)。
>このレベルの問題性を指標とするならば、完全に既知で
>ない DLL を DllMain() の中から LoadLibray() を呼
>び出す時点でマズいです。

不勉強で申し訳ないのですが、具体的に何がマズイんでしょうか?
LoadLibrary() ではなく静的にロードした場合は大丈夫とか
そういったことでしょうか??
#一応 LoadLibrary() した後にロードした DLL に
 関数があるかどうかチェックしてはいますが…。

[ ]
RE:11038 秀丸終了時に DLL を ...No.11039
える さん 02/01/20 21:33
 
>#秀丸にあまり関係ない話で申し訳ないですm(_ _)m。>ALL

ですね;

>>>まったくその通りですが、malloc()(new) を呼んだら
>>>必ず free()(delete) しなければ気が済まない、そんな
>>>ところです(笑)。
>>このレベルの問題性を指標とするならば、完全に既知で
>>ない DLL を DllMain() の中から LoadLibray() を呼
>>び出す時点でマズいです。
>不勉強で申し訳ないのですが、具体的に何がマズイんでしょうか?

ごめんなさい、勘違いです。
DllMain() から呼び出してはマズイ場合があるのは同期関数でした。

[ ]
RE:11039 秀丸終了時に DLL を ...No.11046
杉浦 まさき さん 02/01/21 01:49
 
えるさん、こんばんは。
杉浦 まさき です。

>ごめんなさい、勘違いです。
>DllMain() から呼び出してはマズイ場合があるのは同期関数でした。

いえ、これはこれで勉強になりましたφ(^^)。
#何故かは気になるところですが、
 これ以上はさすがに迷惑になりそうなんで
 自分で調べてみま〜す。

[ ]