dllの中でスレッドを生成し、起動しています。
通常、dllは、正常に終了するので終了する前にスレッドを停止しています。
あるとき、dllが正しく終了しなかった場合にスレッドが生きたままになっている
生きたままのスレッドが生成元のdll(すでに死んでいるdll)をコールして
アプリエラーになっているような気がしています。
以下のやり方で問題があれば、ご指摘ください。
1.DLLのロード
2.スレッドクラスの作成
スレッドの起動(_beginthreadex)
イベントの作成(CreateEvent)
3.DLLの異常等による終了
4.スレッドクラスのデストラクタ
イベントの終了
スレッド未終了時スレッドの強制終了(TerminateThread)
ハンドラのクローズ(CloseHandle)
・デストラクタでスレッドの終了することは問題ないでしょうか?
・スレッドが起動中に強制終了させていますが、スレッドを強制終了することは、
一般なことなのでしょうか?
・すでに死んでいるdllがコールされないような方法はありますか?
ちなみに開発環境は、
Windows2000
VC++6.0
WindowsAPI
です。
情報が少ないかもしれませんが、ご存知の方、ご教授お願いします。
そもそも、「DLL が死ぬ」とはどういう事態なのでしょうか。
何らかのエラーによって、ロードされた DLL が勝手にアンロードされる?
そのような話を聞いたことはありませんが、どのような原因により引き起こされるので
しょうか?
> ・デストラクタでスレッドの終了することは問題ないでしょうか?
> ・スレッドが起動中に強制終了させていますが、スレッドを強制終了することは、
> 一般なことなのでしょうか?
実行中のスレッドを TerminateThread で強制終了させることは、推奨される手段ではあ
りません。
ただし、DLL が死ぬ(という事象がどういうものだかいまいちわかりませんが、アンロー
ドされるということであれば)ような重要な異常事態下にあっては、推奨されるのされな
いのと言っている場合ではないので、緊急手段としてはあり得るものだと思います。
デストラクタでスレッドを終了させることの是非についても、上記のことが言えます。
つまり、正常系におけるクラスのデストラクタで TerminateThread を使うことは推奨さ
れませんが、異常事態下においては検討に値する手段だということです。
正常な状態では、スレッドの強制終了は望ましくありませんから、スレッドに何らかの
「終了せよ」という信号(フラグなりイベントなりメッセージなり)を送って、スレッド
が自然終了するまで待機するということになるでしょう。
これがパフォーマンス的に許容できるのであれば、デストラクタ中でスレッドを終了させ
ることは問題ありません。
あるいは、スレッドに終了信号を送ったら、スレッドは放っておいても正常に終了すると
いう期待により、終了を待たずにデストラクタを抜けてしまうというのもありでしょう。
そういう挙動をしても問題の無い設計になっているのであれば、これでも構わないと思い
ます。
スレッドクラスのオブジェクトは dll 内の静的変数なのかな?
で、DllMain でのデタッチ通知とクラスのデストラクタの両方に
スレッド終了のための処理がある、てこと?
DLL のデタッチ通知と DLL 内変数のデストラクタは _DllMainCRTStartup から
順番に同一スレッドで呼び出されるだけのものなので、
デストラクタが呼び出されるなら、その前にデタッチ通知は行われているはず。
何か異常が起きて DLL がデタッチ通知無しにアンロードされてしまうというなら
デストラクタも動かないですよ?
#「DLL が死ぬ」の意味は、やっぱり誰もわからないと思います(^^;
#スレッドやモジュールについてちゃんと理解してるなら、
#もう少し具体的に説明してほしいところですが...
直接の回答ではなくて、感想になるかも知れませんが
何点か指摘させて下さい。
1.↓のような感触がるのであれば(なくとも)、対応するコードを入れる。
>生きたままのスレッドが生成元のdll(すでに死んでいるdll)をコールして
>アプリエラーになっているような気がしています。
すなわち、スレッドルーチンの方に↓のようなコードがありますか?
[手順1]dll(の関数)をコールする
[手順2]正常であれば、そのまま正常処理を続行する。
何らかの異常があれば、「_endthread」でスレッドを終了する。
#何らかのメッセージを出してからの終了の方が尚良し。
#あくまでも、スレッドの方は正常にまだ動いていることが前提。
2.「TerminateThread」は通常でも、スレッドの終了としては問題が
あるようです。(MSDN等を参照)その上さらに、スレッドを生成
した「dll」が機能していないにも関わらず、「dll」から使うとしたら
まともに動くのですか?
#dllをロードした所に、ハンドルのコピーがあれば可能かとも考えます。
3.dllの異常原因の探求も怠りなくして頂きたい。
4.私の知識不足かも知れませんが、「スレッドクラスの作成」?
↑の意味が分からないです。「WindowsAPI」で「クラスの作成」?
「クラス」って、オブジェクト指向のC++(問題はあると思いますが)
とか、JAVAで使われてはいるようですが・・・。
Cインターフェースの「WindowsAPI」で作れるのですか? それとも
「スレッドクラスの作成」は「スレッドオブジェクトの作成」でしょうか?
だから、「デストラクタでスレッド・・・」の意味も分かりません。
5.「dll」単独、「スレッド」単独でも質問が来ています。
ですから、「dll」からの「スレッドの作成」はかなりの難問です。
>以下のやり方で問題があれば、ご指摘ください。
とのことですが、自信を持って指摘できる方はそう多くないと思います。
#私もこのような使い方をしてませんので、自信はありません。
>・デストラクタでスレッドの終了することは問題ないでしょうか?
一般的な終了処理であれば、できるのであればメモリーリークの
問題が発生するので、した方が良いかと思います。
>・スレッドが起動中に強制終了させていますが、
>スレッドを強制終了することは、一般なことなのでしょうか?
通信相手が機能してないときは、コードとしては「TerminateThread」
を使うことがあると思います。しかし、一般的かと聞かれると・・・?
いつでも正常とは限らないので、コードとしては必要と思います。
しかし、正常でないときの原因の調査が肝要と思います。
>・すでに死んでいるdllがコールされないような方法はありますか?
???
上で指摘した[手順1、2]のコードを普通であれば入れると思いますが。
あと、要望として(要点をまとめた)ソースも提示して下さい。
私が理解できるかどうかは別としても、ソースがあれば具体的に
解決できる可能性も高まる思います。
面倒なので、読む側で「です・ます」調に読み替えてくださいね。(笑)
exe や dll はコードを実行しない。
exe や dll 内のコードを実行するのはスレッドだ。
スレッド無しにコードは勝手に動かない。
exe が WinMain 関数を、dll が DllMain 関数を実行するのではない。
スレッドが exe 内の WinMain 関数や dll 内の DllMain 関数を実行するのだ。
exe が死んだ、dll が死んだ、などと表現することはよくあるが、
それは通常、exe や dll のコードを実行しているスレッドがメモリアクセス
違反で停止したり、無限ループに陥っていることを指す。
今回の「dll が死んだ」表現には2~3種類の解釈ができる。
1. dll を実行中の別のスレッドが異常停止/無限ループしている
2. dll 内のコードを実行した別のスレッドが dll 内メモリを破壊してしまった
3. dll がアンロードされてメモリ上から存在しなくなっている
2. は「死んだ」という表現にはちょっとそぐわないかもしれない。
メモリ破壊の結果、1. に至って初めて「死んだ」というのが普通だろう。
ただ、いずれのケースにしろ一般的には救済不可能だと思う。
3. のケースではスレッド制御関数自身が dll モジュール内に書かれているコードなら
(今回のはたぶんそうだが)、dll 関数を呼ばないようにするもへったくれもない。
dll がアンロードされた瞬間、直ちにメモリアクセス違反になるだろう。
なにしろスレッド制御関数内に書かれているコードもアンロードされてしまうのだか
ら。
それ以外のケースでは、正常に動作しているスレッドは、
そのまま問題なく dll 内コードを実行できるかもしれないし、
実行できなくなるかもしれない。
さて、「dll が死んだ」とは、どういう状況ですかね?
続けて、
DllMain 関数内ではあまり複雑なことをしないほうがいい。
例えば、スレッドの起動や終了待ち合わせ。
スレッドを起動すれば、起動されたスレッドは制御関数の実行を開始する前に
DLL_THREAD_ATTACH を引数として DllMain 関数を呼び出すだろう。
待機しているスレッドが終了すれば、そのスレッドは制御関数を抜けた後で
DLL_THREAD_DETACH を引数として DllMain 関数を呼び出すだろう。
スレッドの起動元や待ち合わせ元がまだ DllMain 関数を実行している最中に、
再び起動されたスレッドがこのような DllMain 関数呼び出しの動作をするのだ。
僕はそんな複雑なのはいやだ。
すなおにスレッド起動用関数と終了用関数をエクスポートして、利用する側の
モジュールに下駄を預けちゃうね。
ごめん、DllMain 関数はシステムによってシリアライズされるから
同時実行はされない。
だから、そもそも DllMain 関数ではスレッドの終了待ちはできない。
たぶんデッドロックする。
> たぶんデッドロックする。
たぶんぢゃなくて確実にします(やったことあります)。
> たぶんぢゃなくて確実にします(やったことあります)。
(´∀`σ)σ
もしかして、質問者はまさにこの類のデッドロックを「死んだ」とか言ってるのかな
ぁ?
...違うか。
システム見えの”真の DllMain”は _DllMainCRTStartup のほうだから、
クラスのデストラクタでも待ち合わせは不可能ですね。
それで、TerminateThread なのか。
デタッチ時の _DllMainCRTStartup に制御が渡った時点で、
もしもまだ対象スレッドが動作していたら、そのスレッドはもはや
穏便に終了することなど絶対に無く、メモリ保護違反になるか、
TerminateThread で手足をもぎ取られて終了するしかない運命だな。
皆さん、貴重なご意見ありがとうございます。
>シャノンさん
「DLLが死ぬ」とは、アンロードされたということです。
ハンドラのクローズをすることでスレッドループから抜けてくるはず
なのですが、
スレッドの途中で止まったまま、WaitForSingleObjectへ来ないため、
ハンドラをクローズしてもスレッドループから抜けてこないことが
わかりました。
アンロードされたあともスレッドが終了できない状態になっていました。
本来は、正しくスレッドが終了するはずなのですが、
「ある状態」のときにスレッドが終了しないのです。
そのため、デストラクタでスレッドが起動中なら、
強制終了しようとしています。
「ある状態」は、異常事態ではなく、普通に起こりうる
状態なので、スレッドが終了するのを待つ必要があります。
パフォーマンス的に許容されるので、スレッドをデストラクタで
終了させているのは、そのままにしておきます。
こんなやり方みんなしているのか不安でしたので。
>popoさん
>スレッドクラスのオブジェクトは dll 内の静的変数なのかな?
>で、DllMain でのデタッチ通知とクラスのデストラクタの両方に
>スレッド終了のための処理がある、てこと?
スレッドクラスは、単にスレッドを持っているクラスです。
dll 内の静的変数ではありません。
デタッチ通知の意味はよく知らないのですが、
終了処理は、デストラクタで行っています。
>・・・さん
異常終了というのは、例外を発生させて終了させています。
一つ分かったのですが、例外を発生させた場合、
デストラクタが呼ばれないことがわかりました。
try{
AAA aaa;
}
catch(...)
{
FuncA();
}
上記の場合、
FuncAでAAAのデストラクタがコールされるはずですが、
ちなみに、AAAのデストラクタでスレッド終了処理が入っています。
>renbar さん
ずばり、その通りでした。
エラーダイアログが表示されるのですが、原因がわからなかったので、
「死んだ」という表現を使っていました。
1.の「スレッドが異常停止してしまっている」ため、「死んだ」ようです。
そのまま、dllがアンロードされてスレッドだけが生き残っており、
これが根本原因でした。
-----
対処方法としては、
1.スレッド終了時には、終了通知を送る。
2.スレッドが終了していない場合(「ある状態」の場合)は、
スレッドが終了するのを待つ。
ここで気になるのが、
・デストラクタでスレッドが終了するのを待っているが、
「ある状態」は、長い時間そのままであることもある。
そのため、INFINITEで永久に待つことになるが、
デストラクタで処理が止まった状態になってしまうが、
「ある状態」から復旧したとき、そのあとの処理が
正しく行われるのでしょうか?
・例外を発生させた場合、デストラクタが呼ばれない
ようですが、本当にそうなのですか?ブレークを張ってても
ブレークで止まりませんでした。
----
皆さんのお陰で解決しそうです。
ありがとうございます。
> 「DLLが死ぬ」とは、アンロードされたということです。
「DLL 内でなんらかの異常が発生した場合に、勝手にアンロードされる」のでしょう
か。そんなことがあるとは思えません。
「自分で意図的にアンロードしたのに、スレッドが上手いこと終了しない」ということ
であれば、「スレッドを終了してからアンロードさせてください」ということになりま
す。
> ハンドラのクローズをすることでスレッドループから抜けてくるはず
> なのですが、
> スレッドの途中で止まったまま、WaitForSingleObjectへ来ないため、
> ハンドラをクローズしてもスレッドループから抜けてこないことが
> わかりました。
ハンドラ?
ハンドル(hThread)をクローズ(CloseHandle)ことを言ってますか?
覚えておいてください。
「スレッドハンドルをクローズしても、そのスレッドが即座に終了することはありませ
ん」。
ついでに言うと、クローズしたハンドルを WaitForSingleObject に渡しちゃダメっす
よ。
> 異常終了というのは、例外を発生させて終了させています。
> 一つ分かったのですが、例外を発生させた場合、
> デストラクタが呼ばれないことがわかりました。
> try{
> AAA aaa;
> }
> catch(...)
> {
> FuncA();
> }
> 上記の場合、
> FuncAでAAAのデストラクタがコールされるはずですが、
少なくともこのコードの通りでは、FuncA の中で aaa のデストラクタが呼ばれることは
ありえません。
どこで例外を発生させています?
>「自分で意図的にアンロードしたのに、スレッドが上手いこと終了しない」ということ
>であれば、「スレッドを終了してからアンロードさせてください」ということになりま
>す。
その通りです。
意図的にアンロードしていたのですが、スレッドが終了していないとは、
思っていませんでした。
>「スレッドハンドルをクローズしても、そのスレッドが即座に終了することはありませ
ん」。
スレッドループから抜けてこないのは、「ある状態」の場合に抜けてこないので、
スレッドハンドルをクローズしても、「ある状態」から復旧しない限り、
永久にループから抜けてきません。
try
{
AAA aaa;
}
catch(...)
{
FuncA();
}
回答、ありがとうございます。
すいません、途中で送信してしまいました。
例外を発生させているのは、
try
{
AAA aaa;
throw e; // ←ココです。
}
catch(...)
{
FuncA();
}
(スレの趣旨と関係ないです)
> 「DLL 内でなんらかの異常が発生した場合に、勝手にアンロードされる」
> のでしょうか。そんなことがあるとは思えません。
SetWindowsHookEx + WH_MOUSE で他のアプリケーションのアドレス空間にロードした
DLLは、その内部で例えばアクセス違反を起こすと、ロード先のアプリケーションは
無事なまま、アンロードされます。