先日はどうもありがとうございました。
もう1点質問させてください。
1 状況
・昔作ったソフトの簡単な改修を行おうとしています。環境はWindows10+delphi6で、動かしている端末もWindows10です。このソフトは安定して動いていました。
・改修内容は、TshellTreeviewを一つ貼り付けて、onChangeイベントに、以下のような動作を加えまたというものです。
if directoryexists(shellTreeview1.path) then
Edit1.text := shellTreeview1.path
・原因がはっきりしないのですが、この改修を加えた前後あたりから、プログラムを終了させたとき、フォームを閉じた直後にアクセス違反がごく稀に生じるようになりました。
頻度としては、50回に1回あるかないかです。
・次のサイトのメモリリークの対策は既に講じました。コンポーネント内にデストラクタを追加して、formclose時に、先にshellTreeview1を解放するようにしました。(このようなメモリリークがアクセス違反に繋がるのかは不明ですが…)
https://fishers.mydns.jp/pc/delphi/treeview/tshellleak.html
2 質問事項
・例えばですが、TshellTreeviewを使うと、プログラムの終了時にエラーが起きることがあるというのは、既に知られた現象なのでしょうか。
Mr.XRAY様のサイトを拝見すると、終了時にエラーが起きるような現象もある様子です。
http://mrxray.on.coocan.jp/Delphi/Others/ShellCtrlComponent.htm#06
・改善点として考えられそうな手があれば、ご教示くださると幸いです。
1点追加です。
TshellTreeviewのautoRefreshというプロパティは有効にしていません。
(有効にするとメモリリークするという情報がありました。アクセス違反が出た頃、一応対策はしていますが、話を簡単にするために有効とはしていません)
FormCloseQueryで終了フラグをたて、
onChangeで終了フラグがたっていたら処理しないようにしてはどうでしょうか?
書き込みありがとうございます。
現時点では、それよりも一つ遅いon closeイベントで、shellTreeview1 .onChange := nil とやってイベントを無効にし、その直後にfreeandnilで解放していました。
一時期、shelltreeviewのonchangeイベントにshowmessageを入れ、formの終了処理時にも発生するのか観察したことがありましたが、showmessageが表示されたことはないため、あまり気にしてはおりませんでした。
あまり理解できていないのですが、こういう処理はFormCloseQueryでやった方が安全なのでしょうか。
Windowsのシャットダウンに巻き込まれるとOnCloseQuery→OnDestroyとOnCloseをすっ飛ばされることがあります。
OnCloseは Action := caFree; 以外ではあまり使わないですね(個人的な感想です)。
開発環境をさらっと流されてるけどDelphi6ですか?
TshellTreeviewに起因するメモリリークで起きているのか、
自身のプログラムに起因してメモリリークが起きてるのか
確認するためにMemCheckを動作させてみてはいかがでしょうか?
Delphi6でMemCheckを動作させるには?
https://www.petitmonte.com/bbs/answers?question_id=2317
書き込みありがとうございます。
イベントのタイミングは少し後かもしれませんが、ShellTreeView1のOnChangeイベントに
「ShowMessage」を1つ挟んで観察してみました。
結果として、メッセージは表示されませんでしたので、イベント自体が発動していない=
不具合は別のところにある、と切り分けたつもりです。
MemCheckについては、若かりし頃挑戦し、当時は何も分からない状況で断念した記憶が
あります。今は当時よりも多少高度なことを理解できるようになっているので、もし今でも
ライブラリがあるなら試してみようと思います。ただ、その前提として、メモリリークはア
クセス違反の原因となり得るのでしょうか。自分の理解としては、きちんとメモリを解放し
なかっただけであって、必要なときに開放してしまっているというケースと比較すると、
むしろアクセス違反は起こりにくくなるのでは?と考えていました。
(もちろん、この理解が誤りであることは大いにあり得ることだと思っています。)
1点追記です。
× 結果として、メッセージは表示されませんでしたので、
○ 結果として、FormClose時にShellTreeView1.OnChangeイベントによるメッセージは表示されませんでしたので、
MemCheckをやってみました。行番号まで返してくれるものだと思いませんでした。
MemCheck version 2.75
TOP 10 Leaks: begin
TOP 10 Leaks: end
Total leak: 0 bytes
*** MEMCHK: Blocks STILL allocated ***
*** MEMCHK: End of allocated blocks ***
*** MEMCHK: Chronological leak information ***
*** MEMCHK: End of chronological leak information ***
*** MEMCHK: Blocks written to after destruction ***
Bad blocks count: 0
*** MEMCHK: End of blocks written to after destruction ***
一応、主だった機能を使った後に終了したのですが、これはメモリリークは
ないと考えてよいのかな?というように見えました。
(ShellTreeViewについては、DelphiXE6のソースを参考に修正していたため、
既にリークの件は解決できていたのだと思います。)
しかし、ShellListViewを張り付けておくと、ごくまれにエラーが出て、再度
外していたものをしばらく使っていてもエラーは出ない(たまたまなのかも
しれませんが…)状況があり、そうするとやはりShellTreeViewに原因がある
のかな、とも思われました。
ちなみに手順はこちらのとおりにやりました。
https://edn.embarcadero.com/print/33696
①MemCheckをプロジェクトに追加し、
②MemChkを一番最初に書き、
③「スタックフレームの生成」「TD32デバッグ情報を含める」を有効にしてから実行、終了しました。
TShellTreeviewを使っているUnitに
Uses ActiveX;
...
initialization
CoInitialize(nil);
finalization
CoUnInitialize;
end.
を付けるのは如何でしょうか?
効果が無かったらすいません。
書き込みありがとうございます。
今、ShellTreeViewのソースを見ましたら、CoInitializeなるコマンドは入っており
ませんでした。これは、COMという機構を使うときに何かを初期化するために
使用するコマンドのようですが、大変恐縮ですが、自分で調べても今一つイメージ
が持てておりません。(現在のスレッドにアパートメント属性を設定・解除するな
どといった説明がありましたが…)
基本的に、ActiveXというユニットを使うときは、これを呼び出した方がよいもの
なのでしょうか。
※過去にIEコンポーネントを使用したタブブラウザを作ったことがありましたが、
そのときは次のような4行の句を入れていました。当時は、「IEコンポーネント
を使うときは入れるものだ」などとしか聞いた記憶がありません。今回
ご提示いただいたコマンド名とも違うようですし。
initialization
OleInitialize(nil);
finalization
OleUninitialize;
何度もすみません。
ShellTreeViewのユニットを見ると、UsesにActiveXがあり、末尾に
initialization
OleInitialize(nil);
finalization
OleUninitialize;
がありました。でもやはりCoInitializeはありませんでした。
CoInitializeという関数が何を行うものか、まだイメージできておりませんが、
http://mrxray.on.coocan.jp/Delphi/plSamples/802_FolderBrowserCallback.htm
こちらのページを参照しますと、フォルダツリーのダイアログを呼び出す際、古いDelphiでは必要な関数のようです。
まずは、ご助言いただいた4行のコードを追加し、様子を見てみたいと思います。
先ほど30回ほど起動してみましたが、やはりエラーが出ました。かなり期待しておりましたので、本当に残念です。
エラーメッセージは、以前こんな感じのものが出ていましたが、今回もこの000236E7というのは同じでした。
EAccessViolationがモジュール Project1.exe の 000236E7 で発生しました。
モジュール 'Project1.exe' のアドレス 004236E7 でアドレス 02355890 に対する読み込み違反がおきました。.
http://mrxray.on.coocan.jp/Delphi/Others/ShellCtrlComponent.htm#06
終了時 TShellTreeView の項目 が選択されてないとこのエラーがでるとか?
終了時でないタイミングで TShellTreeView 解放して エラーでるの?
書き込みありがとうございます。
現時点では、oncloseで処理しておりましたが、HFUKUSHI様の書き込みを参考に、shelltreeviewの解放のタイミングをonclosequeryにずらしてみようと思います。
なお、これまで、onChangeイベントには
if directoryexists(shellTreeview1.path) then
Edit1.text := shellTreeview1.path
と書いていましたが、ここでは非選択時の動作は考慮しておりませんでした。(shelltree viewでは、最初からデスクトップが選択され、フォルダを削ってもその次のフォルダなどが必ず選択される仕様であったため。実際、直接ここからのエラーは出ませんでした。)
ご指定のURLでは、ここでの動作可否判定で、
if ShellTreeView1.Selected <> nil then
といれると終了時エラーになるかもというお話ですので、
当方では、
if ShellTreeView1.SelectionCount > 0 then
などと入れて試してみようと思います。(結局shell tree view内部でやっていることは同じなのかもしれませんが)
あれからも、時間を見て修正を試みていましたが、結局は原因不明のアクセスエラーは残り、解決することはできませんでした。
半ば諦めて削除し、しばらく使ってみましたが、エラーは発生せず、やはりshelltreeviewに起因するものだったのかな、という感触ではおります。
再現性が低く、ここまでの作業を通じてエラーの特定や除去は困難だと判断したこと、今回はbrowseforfolder関数など、回避策があることを考慮し、今回はshelltreeviewを削除しておしまいにしたいと思います。
今回は、いろいろとご助言くださり、ありがとうございました。解決はしなかったものの、今後も生かせるデバッグの手法などに関して知識を得ることができ、貴重な機会になりました。
ツイート | ![]() |