LoginSignup
4
5

More than 3 years have passed since last update.

【.NET】進捗ダイアログ画面で動的DLLの進捗状況を表示する

Last updated at Posted at 2018-07-28

はじめに

前記事「【.NET】進捗ダイアログ画面で外部プロセスの進捗状況を表示する」では、取り込みバッチ処理を外部プロセス(コンソールアプリケーション)で実現しましたが、クラスライブラリー(DLL)にしたいという要望で再度サンプルを作ることになりました。

プログラム仕様

クラスライブラリー(DLL)は静的な参照とはせず動的な参照とします。進捗状況をイベントで受け取りプログレスバーに進捗状況を表示することにしました。
前回同様、バックグラウンド処理にはBackgroundWorkerコンポーネントを使用します。

※今回はBackgroundWorkerコンポーネントを使わなくでも出来ましたが、前記事に合わせました。

ソースコード

進捗ダイアログ画面

frmDoWork.png

バックグラウンド処理にはBackgroundWorkerコンポーネントを使用しています。

frmDoWork
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
using System.IO;

namespace SampleMain
{
    public partial class frmDoWork : Form
    {
        // 取込処理名
        public string ExecuteName { get;  set; }
        // 取込処理の引数
        public string Arguments { get; set; }

        // 動的DLL処理用
        private Type _myType = null;
        private Object _instance = null;

        // コンストラクタ
        public frmDoWork()
        {
            InitializeComponent();
        }

        // 画面ロード時
        private void frmDoWork_Load(object sender, EventArgs e)
        {
            // アセンブリ名を使ってクラス ライブラリーを動的に読み込み
            string baseName = Path.GetFileNameWithoutExtension(ExecuteName);
            Assembly assembly = Assembly.Load(baseName);
             _myType = assembly.GetType(baseName + ".Program");
            _instance = Activator.CreateInstance(_myType);

            // アセンブリ内のクラスの Update イベントの EventInfo を取得
            EventInfo eventInfo = _myType.GetEvent("Update");
            var methodInfo = this.GetType().GetMethod("OnUpdate");

            Delegate handler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, methodInfo);

            // EventInfo に対してイベント ハンドラーを追加
            eventInfo.AddEventHandler(_instance, handler);
         }

        // 画面初回表示時
        private void frmDoWork_Shown(object sender, EventArgs e)
        {
            // 閉じるボタンを無効にする
            btnClose.Enabled = false;

            // ProgressChangedイベントが発生するようにする
            bgWorker.WorkerReportsProgress = true;
            // 処理を開始する
            bgWorker.RunWorkerAsync();
        }

        // 画面を閉じる
        private void btnClose_Click(object sender, EventArgs e)
        {
            Close();
        }

        // 取込処理
        private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bgWorker = (BackgroundWorker)sender;

            // 処理を開始する
            int result = (int)_myType.InvokeMember("Main", BindingFlags.InvokeMethod, null, _instance, new object[] { Arguments });

            // 結果を設定する
            e.Result = result;
        }

        // 途中経過イベント処理
        private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            // ProgressBarの値を変更する
            prbDowork.Value = e.ProgressPercentage;
            // タイトルのテキストを変更する
            lblTitle.Text = (e.ProgressPercentage).ToString() + " %";
        }

        // 取込処理が終わったときに呼び出される
        private void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                // エラーが発生したとき
                lblTitle.Text = "エラー:" + e.Error.Message;
            }
            else
            {
                // ProgressBarの結果を取得する
                int result = (int)e.Result;

                if(result == -1)
                {
                    // エラーで中断したとき
                    lblTitle.Text = "処理を中断しました。";
                }
                else
                {
                    // 正常に終了したとき
                    prbDowork.Value = prbDowork.Maximum;
                    lblTitle.Text = "完了しました。";
                }
            }

            // 閉じるボタンを有効に戻す
            btnClose.Enabled = true;
        }


        // 進捗値の更新
        public void OnUpdate(object sender, EventArgs e)
        {
            PropertyInfo prop_value = e.GetType().GetProperty("value");
            int percentage = Convert.ToInt32(prop_value.GetValue(e, null));

            // ProgressChangedイベントハンドラを呼び出し
            bgWorker.ReportProgress(percentage);
        }
    }
}

【2020/05/07追記】
.NET Core 3.1で試しました。下記の部分を変更することで対応出来ます。

Assembly assembly = Assembly.Load(baseName);

.NET Core 3.1以降の場合、AssemblyLoadContextクラスを使用します。今回はアンロードについては考慮していません。
AssemblyLoadContext

var myDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var assemblyPath = Path.Combine(myDirectory, ExecuteName);
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);

メイン画面

実行ボタンで進捗ダイアログ画面を表示する

frmMain.cs
private void btnExecute_Click(object sender, EventArgs e)
{
    // メッセージボックスを表示する
    DialogResult result = MessageBox.Show("実行します。よろしいですか ? ",
                                          "処理実行",
                                          MessageBoxButtons.YesNo,
                                          MessageBoxIcon.Question,
                                          MessageBoxDefaultButton.Button2);
    if (result == DialogResult.Yes)
    {
        // 処理実行
        frmDoWork frm = new frmDoWork();
        // 実行ファイル名 サンプルなので固定
        frm.ExecuteName = "SUB00001.dll";
        // 進捗画面表示
        frm.ShowDialog();
    }
}

取込バッチ処理

クラスライブラリー(DLL)で、進捗状況のイベントを発生させ進捗値を渡します。
処理内容はサンプルなので適当です。

Program.cs
using System;

namespace SUB00001
{
    public class UpdateEventArgs : EventArgs
    {
        public int value { get; set; }
    }

    class Program
    {
        // 更新されると起きるイベント
        public event EventHandler Update;

        public int Main(string[] args)
        {
            if (!SubProccess10()) return -1;
            if (!SubProccess30()) return -1;
            if (!SubProccess50()) return -1;
            if (!SubProccess70()) return -1;
            if (!SubProccess90()) return -1;

            // 完了
            SetProgress(100);

            return 0;
        }

        // 処理10%
        private bool SubProccess10()
        {
            System.Threading.Thread.Sleep(200);

            SetProgress(10);

            return true;
        }

        // 処理30%
        private bool SubProccess30()
        {
            System.Threading.Thread.Sleep(200);

            SetProgress(30);

            return true;
        }

        // 処理50%
        private bool SubProccess50()
        {
            System.Threading.Thread.Sleep(200);

            SetProgress(50);

            return true;
        }

        // 処理70%
        private bool SubProccess70()
        {
            System.Threading.Thread.Sleep(200);

            SetProgress(70);

            return true;
        }

        // 処理90%
        private bool SubProccess90()
        {
            System.Threading.Thread.Sleep(200);

            SetProgress(90);

            return true;
        }

        // 進捗状況を標準出力に出力する
        private void SetProgress(int value)
        {
            // 更新イベントを起こす
            UpdateEventArgs e = new UpdateEventArgs();
            e.value = value;

            Update?.Invoke(this, e);
        }
    }
}

実行結果

成功

import_progress.gif

失敗

SubProccess70()の戻り値を true -> false に変更。

import_progress2.gif

最後に

今回は動的にイベントハンドラーを追加するところと、EventArgsの値を取得する方法が分かるまで苦労してしまいました。

約10年前に複数のWindowsフォームの動的DLLを使ったシステムを構築したのを思い出しました、ユーザーのところに修正したモジュールの差し替えにいった同僚が遅く帰ってきました。
聞くところによるとユーザーの仕事終わりでメニュー画面を閉じるのを待っていたそうです。DLLファイルの差し替えなんてすぐだろうと思ってたのですが、動的DLLを呼び出しているメニュー画面を閉じないとエラーで差し替え出来ないのです。このあたりは反省した覚えがあります。
動的DLLの解放について - @IT掲示板

次回は、動的DLLを差し替えできるようにしてみたいと思います。

参照

4
5
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5