none
孫フォームが開いたままプログラムを終了させると例外が発生する場合について RRS feed

  • 質問

  • スタートフォームで利用DLLのOpen関数を呼び、Closingで利用DLLのClose関数を呼んでいます。
    子フォームから孫フォームを開いています。孫フォームのActivatedで利用DLLの一般関数を呼んでいます。
    子フォームは閉じても孫フォームは開いたままで人間が閉じないとそのままです。

    孫フォームが開いたままの状態でプログラムを終了させると、利用DLLのClose関数が先に
    動き、孫フォームのActivateが後に動く結果、利用DLLのCloseが済んでいるにも関わらず、
    利用DLLの一般関数が呼び出され例外が発生します。

    スタートフォームから子フォームを介して管理はできないこともありませんが、
    孫まで一々管理するのは煩いですし、正確にいうと孫ではなく色んなフォームから
    利用されるフォームです。特定の子のフォームを介して管理することも出来ません。

    スタートフォームのClosingまたはClosedが起こる前に、開いている色んな
    フォームから利用されるフォームのCloseを上手くやる方法はありませんか?

     

    2009年11月26日 2:40

回答

  • DLL の管理をするためにフォームの動作を制限するのは、あまり良い策であるようには思えません。
    フォームが閉じられていないと問題になるような DLL であればしかたありませんが、この場合はそうではないのですよね。

    それは別として、すべてのフォームに対して閉じる操作をしたい場合、Application.OpenForms が使えるかもしれません。
    また、Close メソッドは、通常は Closing イベントが終わるまで待機し、Closing イベントがキャンセルされなかった場合に Closed イベントの終了を待機するため、期待されているような動作はすると思います。
    # フォームだけのプログラムを作成し、それぞれのイベントに時間のかかる処理などを書いてみるとよいでしょう
    • 回答としてマーク クサキ 2009年11月28日 0:44
    2009年11月26日 11:42
  • スタートフォームのClosingで全体が終わるイベントを出し、該当する他のフォームが
    それをキャッチし、this.Closeすれば先に子や孫のフォームを閉じ、スタートフォーム
    のClosedでDLLのClose関数を呼べ上手く行きました。
    また、孫フォームのClosing のところで2秒程度Waitさせても、スタートフォームの
    Closedが先に実行されることもなく上手く行きまた。

    ただ、一般的には、イベントハンドラーを登録するするコーディングの時、
    孫フォームにスタートフォームのクラス名が必要で、利用される孫フォームが
    スタートフォームのクラス名を書かなければならず面白くありませんでした。

    私の実際のシステムの場合は、スタートフォームから、全てのベースになる1つの
    子フォームを読んでいまして、そこでDLLのOpen、Closeを行っています。
    全てのフォームはこのフォームの存在をベースにしていますので、
    そのクラス名を使っても問題ありませんでした。

    説明を読んだ限りですが、Application.OpenForms でも上手く行きそうでした。
    私のように沢山のフォームを使っている場合、なかなか便利なようです。

    GCに関連して追加の質問をさせてください。
    子フォームが開いている状態でスタートフォームを閉じた場合、子フォームも消えて
    いましたので、それで良いものだと思っていましたが、子フォームのClosingやClosedは
    通っていませんでした。アンマネージリソースをDisposeした方が良いように、
    Formも明示的に、Closeをしてやった方が良いのでしょうか?
    Closeしても実際には残っていてGCの対象になるのでしょうか?
    Closeをしなくても、GCの対象になるのでしょうか?それなら、どちらでも同じに思えますが?


    >  スタートフォームは先に子フォームを閉じる処理の責任を持ち、子フォームは孫フォームを
    > 閉じる処理の責任を持つように設計・実装すれば、スタートフォームは孫のことを(直接は)気にしなくてもよいのでは?

    私のシステムは仕事ごとに子フォームを開き行っています。
    また、子フォームがいっぱい表示されると煩いので基本的に別の子フォーム
    を開く時は、今開いている子フォームは閉じています。
    今回の孫フォームは全体に共通で使えるグラフで、子フォームを閉じても、
    残したままにしたいので、子フォームを閉じる時に閉じれません。

     

    • 回答としてマーク クサキ 2009年11月28日 0:44
    2009年11月27日 2:53
  • ただ、一般的には、イベントハンドラーを登録するするコーディングの時、
    孫フォームにスタートフォームのクラス名が必要で、利用される孫フォームが
    スタートフォームのクラス名を書かなければならず面白くありませんでした。

    私が書きたかったのは、親は子の面倒だけを見る、子は孫の面倒だけを見るという意図です。
    親の FormClosing イベントハンドラの中で自分が知っている子を閉じる、すると、子の FormClosing イベントハンドラが呼び出されるので子は自分の知っている孫を閉じるということをすれば、孫は親を知る必要がありません。

    # 問答において、「おもしろくない」という表現は避けた方が良いのでは?

    説明を読んだ限りですが、Application.OpenForms でも上手く行きそうでした。
    私のように沢山のフォームを使っている場合、なかなか便利なようです。

    便利かもしれませんが、それに頼っていると、あとで困るかもしれません。
    例えば、このフォームだけは生き残らせたいとか、仕様追加・変更が相次ぐと悲鳴を上げたくなります。

    Formも明示的に、Closeをしてやった方が良いのでしょうか?
    Closeしても実際には残っていてGCの対象になるのでしょうか?
    Closeをしなくても、GCの対象になるのでしょうか?それなら、どちらでも同じに思えますが?

    アプリケーション終了時に GC が実行される保障はありません。
    従って、必ず実行されないとまずい処理があるのであれば、きちんと Close / Dispose するべきでしょう。

    なお、本当に親子関係になっているフォームの場合、親が閉じられる前に子が閉じられます。

    今回の孫フォームは全体に共通で使えるグラフで、子フォームを閉じても、
    残したままにしたいので、子フォームを閉じる時に閉じれません。

    それは、子の子(孫)とは言えません。子フォームから見ると独立したフォームですよね。
    基本的に、誰かの子のような関係にした方が良いと思います。

    子を閉じても生き残らせたいのであれば、親フォーム(=スタートフォーム)に管理させてはいかがでしょうか。
    子フォームから「共通フォームを表示しろ!」というイベントを発行し、親フォームに用意したイベントハンドラで共通フォームを表示するとか、そういった組み方もできるかと思います。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク クサキ 2009年11月28日 0:44
    2009年11月27日 15:14
    モデレータ

すべての返信

  • DLL の使用を管理したいのでしょうか? 他の用途もあって Form の閉じる順序を管理したいのでしょうか?
    DLL の使用状況の管理だけであれば、DLL の一般関数を使用しているフォームがすべて破棄されてから Close が呼ばれるように管理すればよいのですよね?
    たとえば、単純に Open を要求した回数で使用状況を管理するならば、以下のようなかんじのコンポーネントにまとめてしまい、これを DLL を使用したいすべてのフォームに張り付けておけば、順序に限らずすべてのフォームが閉じて refCount が 0 になってから Close が呼ばれるようになります。

    Open,Close にコストがかかるため、スタートフォームで Open し、子/孫だけを閉じても Close されたくないなら、スタートフォームにもこのコンポーネントを配置しておくことで、親フォームで Open し、すべてのフォームを閉じるまで Close されない状態になります。

    using System.ComponentModel;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Threading;
    
    public class BeepComponent : Component
    {
        private IContainer components = null;
    
        public BeepComponent()
        {
            this.components = new Container();
        }
    
        public BeepComponent(IContainer container) : this()
        {
            container.Add(this);
        }
    
        protected override void Dispose(bool managed)
        {
            if (managed && (this.components != null))
            {
                this.components.Dispose();
                this.components = null;
    
                // 無効化してから終了
                this.Enabled = false;
            }
    
            Debug.Assert(!this.Enabled);
            base.Dispose(managed);
        }
    
        private bool enabled = true;
        
        [DefaultValue(false)]
        public bool Enabled
        {
            get { return this.enabled; }
            set 
            {
                if (value)
                {
                    this.enabled = this.DesignMode || InitDLL();
                }
                else if (this.Enabled)
                {
                    this.enabled = false;
    
                    if (!this.DesignMode) TermDLL();
                }
            }
        }
    
        private static int refCount = 0;
    
        /// <summary>DLL の初期化を実施する</summary>
        private static bool InitDLL()
        {
            if (0 == Interlocked.Increment(ref refCount))
            {
                // Open() 等、DLL の初期化関数などを呼び出し
                // 成功した場合は true、失敗した場合には false を返す
            }
    
            return true;
        }
    
        /// <summary>DLL の終了処理を実施する</summary>
        private static void TermDLL()
        {
            if (0 == Interlocked.Decrement(ref refCount))
            {
                // Close() 等、DLL の終了関数などを呼び出す
            }
        }
    
        #region /* DLL の一般関数 */
    
        public enum BeepSoundType : int
        { Simple = -1, Asterisk = 0x40, Exclamation = 0x30, Hand = 0x10, Question = 0x20, Default = 0 };
    
        public bool Beep(BeepSoundType type)
        {
            Debug.Assert(this.Enabled);
            return MessageBeep(type);
        }
    
        #endregion
    
        #region /* P/Invoke Defines */
    
        [DllImport("USER32.DLL", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        private extern static bool MessageBeep([In] BeepSoundType type);
    
        #endregion
    }
    
    2009年11月26日 4:16
  • DLLの管理ということになるのでしょうか、DLLのCloseの後にDLLの一般関数をCallすることがないようにしたいだけです。
    スタートのフォームを終わる時に、開いている他のフォームを先に閉じることができたら目的は達成できるのではと考えています。
    それはごく自然な要求で、そうした方が良いような気がします。簡単にできるのかなとも思っていました。

    わざわざ、具体的なコーディングまで書いて頂きましたが、私にはなななか理解が難しいです。
    もう少し、勉強してみたいとも思いますが、今私が今できる範囲で考えてみました。

    スタートフォームのClosingで全体が終わるイベントを出し、該当する他のフォームが
    それをキャッチし、this.Closeすれば先に他のフォームが消えて、スタートフォームの
    ClosedでDLLのClose関数を呼べば上手く行くような気がしまが、如何でしょう。
    ただ、この場合、他のフォームのClosedが終わるまで、スタートフォームのClosedは
    動かないと保障されるものですか?

     

    2009年11月26日 9:52
  • DLL の管理をするためにフォームの動作を制限するのは、あまり良い策であるようには思えません。
    フォームが閉じられていないと問題になるような DLL であればしかたありませんが、この場合はそうではないのですよね。

    それは別として、すべてのフォームに対して閉じる操作をしたい場合、Application.OpenForms が使えるかもしれません。
    また、Close メソッドは、通常は Closing イベントが終わるまで待機し、Closing イベントがキャンセルされなかった場合に Closed イベントの終了を待機するため、期待されているような動作はすると思います。
    # フォームだけのプログラムを作成し、それぞれのイベントに時間のかかる処理などを書いてみるとよいでしょう
    • 回答としてマーク クサキ 2009年11月28日 0:44
    2009年11月26日 11:42
  • スタートフォームから子フォームを介して管理はできないこともありませんが、
    孫まで一々管理するのは煩いですし、正確にいうと孫ではなく色んなフォームから
    利用されるフォームです。特定の子のフォームを介して管理することも出来ません。
    スタートフォームは先に子フォームを閉じる処理の責任を持ち、子フォームは孫フォームを閉じる処理の責任を持つように設計・実装すれば、スタートフォームは孫のことを(直接は)気にしなくてもよいのでは?(子フォームに任せる考え方)
    それができない事情があるのでしょうか?

    1.スタートフォームの FormClosing イベントで子フォームを Close する。
    2.子フォームの FormClosing イベントで 孫フォームを Close する。
    3.孫フォームの FormClosing イベント。
    4.孫フォームの FormClosed イベント。
    5.子フォームの FormClosed イベント。
    6.スタートフォームの FormClosed イベント → ここで DLL を Close する。

    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2009年11月26日 14:58
    モデレータ
  • スタートフォームのClosingで全体が終わるイベントを出し、該当する他のフォームが
    それをキャッチし、this.Closeすれば先に子や孫のフォームを閉じ、スタートフォーム
    のClosedでDLLのClose関数を呼べ上手く行きました。
    また、孫フォームのClosing のところで2秒程度Waitさせても、スタートフォームの
    Closedが先に実行されることもなく上手く行きまた。

    ただ、一般的には、イベントハンドラーを登録するするコーディングの時、
    孫フォームにスタートフォームのクラス名が必要で、利用される孫フォームが
    スタートフォームのクラス名を書かなければならず面白くありませんでした。

    私の実際のシステムの場合は、スタートフォームから、全てのベースになる1つの
    子フォームを読んでいまして、そこでDLLのOpen、Closeを行っています。
    全てのフォームはこのフォームの存在をベースにしていますので、
    そのクラス名を使っても問題ありませんでした。

    説明を読んだ限りですが、Application.OpenForms でも上手く行きそうでした。
    私のように沢山のフォームを使っている場合、なかなか便利なようです。

    GCに関連して追加の質問をさせてください。
    子フォームが開いている状態でスタートフォームを閉じた場合、子フォームも消えて
    いましたので、それで良いものだと思っていましたが、子フォームのClosingやClosedは
    通っていませんでした。アンマネージリソースをDisposeした方が良いように、
    Formも明示的に、Closeをしてやった方が良いのでしょうか?
    Closeしても実際には残っていてGCの対象になるのでしょうか?
    Closeをしなくても、GCの対象になるのでしょうか?それなら、どちらでも同じに思えますが?


    >  スタートフォームは先に子フォームを閉じる処理の責任を持ち、子フォームは孫フォームを
    > 閉じる処理の責任を持つように設計・実装すれば、スタートフォームは孫のことを(直接は)気にしなくてもよいのでは?

    私のシステムは仕事ごとに子フォームを開き行っています。
    また、子フォームがいっぱい表示されると煩いので基本的に別の子フォーム
    を開く時は、今開いている子フォームは閉じています。
    今回の孫フォームは全体に共通で使えるグラフで、子フォームを閉じても、
    残したままにしたいので、子フォームを閉じる時に閉じれません。

     

    • 回答としてマーク クサキ 2009年11月28日 0:44
    2009年11月27日 2:53
  • 私のシステムは仕事ごとに子フォームを開き行っています。
    また、子フォームがいっぱい表示されると煩いので基本的に別の子フォーム
    を開く時は、今開いている子フォームは閉じています。
    今回の孫フォームは全体に共通で使えるグラフで、子フォームを閉じても、
    残したままにしたいので、子フォームを閉じる時に閉じれません。
    この部分を読んでて思ったのですが、問題となっている孫フォームを子フォームに昇格し、
    子フォームから当該フォームを起動するというのはどうですか?

    > Formも明示的に、Closeをしてやった方が良いのでしょうか?

    GC の対象にはなるでしょうが、いらなくなったら Close してやった方がいいかと思います。
    以下のスレッドを参考にして頂くといいかと思います。

    アンマネージリソース と Dispose と GC の関係について
    2009年11月27日 4:00
    モデレータ
  • ただ、一般的には、イベントハンドラーを登録するするコーディングの時、
    孫フォームにスタートフォームのクラス名が必要で、利用される孫フォームが
    スタートフォームのクラス名を書かなければならず面白くありませんでした。

    私が書きたかったのは、親は子の面倒だけを見る、子は孫の面倒だけを見るという意図です。
    親の FormClosing イベントハンドラの中で自分が知っている子を閉じる、すると、子の FormClosing イベントハンドラが呼び出されるので子は自分の知っている孫を閉じるということをすれば、孫は親を知る必要がありません。

    # 問答において、「おもしろくない」という表現は避けた方が良いのでは?

    説明を読んだ限りですが、Application.OpenForms でも上手く行きそうでした。
    私のように沢山のフォームを使っている場合、なかなか便利なようです。

    便利かもしれませんが、それに頼っていると、あとで困るかもしれません。
    例えば、このフォームだけは生き残らせたいとか、仕様追加・変更が相次ぐと悲鳴を上げたくなります。

    Formも明示的に、Closeをしてやった方が良いのでしょうか?
    Closeしても実際には残っていてGCの対象になるのでしょうか?
    Closeをしなくても、GCの対象になるのでしょうか?それなら、どちらでも同じに思えますが?

    アプリケーション終了時に GC が実行される保障はありません。
    従って、必ず実行されないとまずい処理があるのであれば、きちんと Close / Dispose するべきでしょう。

    なお、本当に親子関係になっているフォームの場合、親が閉じられる前に子が閉じられます。

    今回の孫フォームは全体に共通で使えるグラフで、子フォームを閉じても、
    残したままにしたいので、子フォームを閉じる時に閉じれません。

    それは、子の子(孫)とは言えません。子フォームから見ると独立したフォームですよね。
    基本的に、誰かの子のような関係にした方が良いと思います。

    子を閉じても生き残らせたいのであれば、親フォーム(=スタートフォーム)に管理させてはいかがでしょうか。
    子フォームから「共通フォームを表示しろ!」というイベントを発行し、親フォームに用意したイベントハンドラで共通フォームを表示するとか、そういった組み方もできるかと思います。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    • 回答としてマーク クサキ 2009年11月28日 0:44
    2009年11月27日 15:14
    モデレータ
  • > 子を閉じても生き残らせたいのであれば、親フォーム(=スタートフォーム)に管理させてはいかがでしょうか。
    > 子フォームから「共通フォームを表示しろ!」というイベントを発行し、親フォームに用意したイベントハンドラで共通フォームを
    > 表示するとか、そういった組み方もできるかと思います。

    最初、親フォーム(=スタートフォーム)で共通のフォーム(最初孫フォームといっていたもの)を
    Newしようとも考えていましたが、現在でも3つあり、子フォームによってはこれらの何れも使わない
    ものもあり、またユーザーによってはその子フォームだけしか使わない場合もあり、メモリが勿体無い
    と思い他の手を考えていました。
    子フォームが必要な時にイベント発生させ、親フォームでnewし表示したら、親子関係も明快になり
    ますし、必要ないメモリを使うこともないようです。私の今回の件では一番綺麗な方法だと思いました。
    2009年11月28日 0:42
  • 最初、親フォーム(=スタートフォーム)で共通のフォーム(最初孫フォームといっていたもの)を
    Newしようとも考えていましたが、現在でも3つあり、子フォームによってはこれらの何れも使わない
    ものもあり、またユーザーによってはその子フォームだけしか使わない場合もあり、メモリが勿体無い
    と思い他の手を考えていました。
    子フォームが必要な時にイベント発生させ、親フォームでnewし表示したら、親子関係も明快になり
    ますし、必要ないメモリを使うこともないようです。私の今回の件では一番綺麗な方法だと思いました。

    それならスタートフォームのパブリックメンバとして共通フォームを管理させ、
    スタートフォームに起動メソッドを用意し、解放責任もスタートフォームに持たせる。
    そしてスタートフォームの Close 時に 共通フォームを Close するようにしておけばいいかと思います。

    そしてスタートフォームのメンバである共通フォームは、初期化を null にしておけば
    メモリを喰うことはありませんから、使わないならそのままにしておけばいいだけの話です。

    コードにすると、こんな感じかと。 


    // スタートフォームの定義
    public partial class StartForm : Form
    {
        // 共通フォーム
        CommonForm _commonForm = null;
    
        // 子フォームから呼ばれる共通フォーム起動処理
        public void ShowCommonForm() {
            _commonForm = new CommonForm();
            _commonForm.Show();
        }
    
        // OnClose を継承し、スタートフォームが閉じるなら共通フォームも閉じる
        protected override void OnClosing(CancelEventArgs e) {
            base.OnClosing(e);
            if (!e.Cancel && _commonForm != null) {
                _commonForm.Close();
            }
        }
    
        // 共通フォームも一緒に解放する
        protected override void Dispose(bool disposing) {
            if (disposing && (components != null)) {
                components.Dispose();
            }
            if (_commonForm != null) {
                _commonForm.Dispose();
            }
            base.Dispose(disposing);
        }
    }

    2009年11月28日 7:49
    モデレータ
  • コードにすると、こんな感じかと。 

    2 点ほど気にかかる点があるというところをお伝えしておきます。

    まず 1 点目としては、親が子を、子が親を知る相互参照が前提であることです。
    例えば、親フォーム(=スタートフォーム)と子フォームが別プロジェクトになっている場合は使えない手法になります。
    (プロジェクト間の相互参照は設定できない仕様である)

    2 点目は、override を「継承」と表現していることです。
    継承と override (オーバーライド)で表現した際に指す事象は異なるものだと思います。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。

    2009年11月28日 8:32
    モデレータ
  • まず 1 点目としては、親が子を、子が親を知る相互参照が前提であることです。
    例えば、親フォーム(=スタートフォーム)と子フォームが別プロジェクトになっている場合は使えない手法になります。
    (プロジェクト間の相互参照は設定できない仕様である)

     確かに親と子が別プロジェクトの場合、相互参照が発生するので不可能な方法です。
    ただし

    > 子フォームが必要な時にイベント発生させ、親フォームでnewし表示したら・・・

    とあるので、子フォームから親フォームに通知できるスコープにあるのならと、この方法で提案してみました。

    2 点目は、override を「継承」と表現していることです。
    継承と override (オーバーライド)で表現した際に指す事象は異なるものだと思います。

    厳密にいえば仰る通りですね。
    この話は面白そうですが、ここで論じると質問から話がずれそうなので、別の機会に聞きたいと思います。
    2009年11月28日 8:51
    モデレータ