C#での自PC内プロセス間通信(WCF版:Windowsサービスにて実装)

IPCでの実装は大昔やってたけど
今回はWCF(Windows Communication Foundation)で実装,
んでWindowsサービスとの通信までやってしまおうか,と
(ローカルPC,リモートPCからWindowsのサービスを制御してしまえ!)

IPC版のプロセス間通信のやり方はこちら


こちらのサイトを参考にしましたです
WCF入門-001 (簡単なサンプル, 自己ホスト形式, コードで記述)@いろいろ備忘録日記さん
WindowsサービスをC#で書く@backyard of 伊勢的新常識さん

Windowsサービス側(かつWCFのホスト側)

WCFの通信で利用するサービスメソッドのエンドポイントインターフェースの定義
namespace WCFWinService.Classes.WCF
{
    //NamespaceのURLは一意な適当URLでよろしいとのこと
    [ServiceContract(Namespace="http://WCFWinService.Samples.WCF/")]
    public interface IWCFWinServiceCore
    {
        //Serviceメソッドの定義
        //WCFにてサービスメソッドとして公開するメソッドはOperationContract属性を付与

        [OperationContract]
        string OutputLogStr(string Str);

        [OperationContract]
        string AddValue(int v);

        [OperationContract]
        string ResultValue();
    }
}

上で定義したインターフェースはクライアント側でも利用します

んで,このインターフェースの実装部を定義
namespace WCFWinService.Classes.WCF
{
    public class ClassWCFWinServiceCore: IWCFWinServiceCore
    {

        public static System.Diagnostics.EventLog EventLog = null;

        public static int    AddValueI   = 0;
        public static int    TickValue   = 0;

        public string OutputLogStr(string Str)
        {
            EventLog.WriteEntry(Str);
            return "ACK";
        }

        public string AddValue(int v)
        {
            AddValueI = v;
            return "ACK";
        }

        public string ResultValue()
        {
            string Result = "ACK ";

            int tmpV = TickValue + AddValueI;
            Result += tmpV.ToString();

            return Result;
        }
    }
}

staticな変数は上位層とのやり取りのために定義しましたです

んで,こいつをWindwosサービス部にてインスタンスを生成して使用します
namespace WCFWinService
{
    public partial class WCFWinServiceMain : ServiceBase
    {
        private const string SERVICE_BASE_ADDRESS = @"net.tcp://localhost:10001/WCFWinService";
        private const string ENDPOINT_OUTPUTLOGSTR      = "OutputLogStr";
        private const string ENDPOINT_ADDVALUE          = "AddValue";
        private const string ENDPOINT_RESULTVALUE       = "ResultValue";

        private bool m_ServiceStartFL;
        private bool m_ServiceTimerJobRunFL;
        private ServiceHost m_host;

        //********************************
        //WinServiceコンストラクタ
        //
        //
        //********************************
        public WCFWinServiceMain()
        {
            InitializeComponent();

            m_ServiceStartFL = false;
            m_host = null;
            m_ServiceTimerJobRunFL = false;
        }

        //********************************
        //WinServiceスタート
        //
        //
        //********************************
        protected override void OnStart(string[] args)
        {
            m_ServiceStartFL = true;
            TMR_Main.Enabled = true;
            m_ServiceTimerJobRunFL = false;

            m_host = null;
        }

        //********************************
        //WinService終了
        //
        //
        //********************************
        protected override void OnStop()
        {
            m_ServiceStartFL = false;
            TMR_Main.Enabled = false;

            if (m_host != null)
            {
                m_host.Close();
                
                m_host = null;
            }
        }

        //********************************
        //WinService タイマイベント
        //
        //
        //********************************
        private void TMR_Main_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (m_ServiceStartFL == false)
            {
                return;
            }

            if (m_ServiceTimerJobRunFL == true)
            {
                return;
            }
            m_ServiceTimerJobRunFL = true;

            try
            { 
                if (m_host == null)
                {
                    ClassWCFWinServiceCore.EventLog = EVL_Main;

                    //アプリケーション構成ファイルを利用せず、ServiceHostを構築する場合は
                    //以下の情報を与える必要がある。
            
                    //    ・サービスの型(インターフェースではなく、実装クラス)
                    //    ・ベースアドレス
                    Type serviceType = typeof(ClassWCFWinServiceCore);
                    Uri baseAddress = new Uri(SERVICE_BASE_ADDRESS);

                    m_host  = new ServiceHost(serviceType, baseAddress);

                    m_host.Opened += M_host_Opened;

                    NetTcpBinding binding  = new NetTcpBinding( SecurityMode.None, false);      //net.tcpでバインド作成
                    Type             contract = typeof(IWCFWinServiceCore);

                    ServiceEndpoint ed1 = m_host.AddServiceEndpoint(contract, binding, ENDPOINT_OUTPUTLOGSTR );
                    ServiceEndpoint ed2 = m_host.AddServiceEndpoint(contract, binding, ENDPOINT_ADDVALUE );
                    ServiceEndpoint ed3 = m_host.AddServiceEndpoint(contract, binding, ENDPOINT_RESULTVALUE );


                    m_host.Open();
                }

                int v = ClassWCFWinServiceCore.TickValue;
                v++;

                if (v > 100000)
                {
                    v = 0;
                }

                ClassWCFWinServiceCore.TickValue = v;
            }
            catch
            {
                m_host = null;
            }
            finally
            {
                m_ServiceTimerJobRunFL = false;
            }
        }

        //********************************
        // HOSTオープン後イベント
        //
        //
        //********************************
        private void M_host_Opened(object sender, EventArgs e)
        {
            //throw new NotImplementedException();
        }
    }
}

今回はNetTcpBindingを利用しましたです
httpとかpipeとかも使える模様

Windowsサービスのスケルトン作成方法は↑のリンクを見てね♪


クライアント側
Winフォームにて実装しましたです

namespace WCFClient
{
    public partial class Form1 : Form
    {
        private const string SERVICE_BASE_ADDRESS = @"net.tcp://192.168.1.150:10001/WCFWinService/";
        private const string ENDPOINT_OUTPUTLOGSTR      = "OutputLogStr";
        private const string ENDPOINT_ADDVALUE          = "AddValue";
        private const string ENDPOINT_RESULTVALUE       = "ResultValue";

        public enum SeverSendMode
        {
            LogWrite,
            AddValue,
            ResultValue
        }

        public Form1()
        {
            InitializeComponent();
            LBLResult.Text = "";
        }

        private void BtnEventLogW_Click(object sender, EventArgs e)
        {
            string st = ServerSendCore(SeverSendMode.LogWrite, TxtEventLogW.Text, 0);
            LBLResult.Text = st;
        }

        private void BtnAddValueI_Click(object sender, EventArgs e)
        {
            int v = 0;
            if (int.TryParse(TxtAddValue.Text, out v) == false)
            {
                v = 0;
            }

            string st = ServerSendCore(SeverSendMode.AddValue, "", v);
            LBLResult.Text = st;
        }

        private void BtnResultValue_Click(object sender, EventArgs e)
        {
            string st = ServerSendCore(SeverSendMode.ResultValue, "", 0);
            LBLResult.Text = st;
        }
        
        //********************************
        //ホストへの送信部主処理
        //
        //
        //********************************
        private string ServerSendCore(SeverSendMode md, string LogStr, int vi)
        {
            string Result = "";
            string AddStr = "";

            switch(md)
            {
                case SeverSendMode.LogWrite:
                    AddStr =  ENDPOINT_OUTPUTLOGSTR;
                    break;
                case SeverSendMode.AddValue:
                    AddStr =  ENDPOINT_ADDVALUE;
                    break;
                case SeverSendMode.ResultValue:
                    AddStr =  ENDPOINT_RESULTVALUE;
                    break;
            }

            // エンドポイントアドレスの構築.
            EndpointAddress endpointAddr = new EndpointAddress(SERVICE_BASE_ADDRESS + AddStr);

            try
            {
                NetTcpBinding myBinding = new NetTcpBinding();
                myBinding.Security.Mode = SecurityMode.None;

                // チャネルを構築しサービス呼び出しを行う
                using (ChannelFactory<IWCFWinServiceCore> channel = new ChannelFactory<IWCFWinServiceCore>(myBinding))
                {
                    // サービスへのプロキシを取得
                    IWCFWinServiceCore service = channel.CreateChannel(endpointAddr);

                    // モードに合わせてサービスメソッド呼び出し
                    switch(md)
                    {
                        case SeverSendMode.LogWrite:
                            Result = service.OutputLogStr(LogStr);
                            break;
                        case SeverSendMode.AddValue:
                            Result = service.AddValue(vi);
                            break;
                        case SeverSendMode.ResultValue:
                            Result = service.ResultValue();
                            break;
                    }

                    // チャネルを閉じる
                    channel.Close();
                }
            }
            catch (CommunicationException ex)
            {
                // エラーが発生
                MessageBox.Show(ex.Message);
            }

            return Result;
        }

    }
}

Windowsサービスは必ず,コマンドプロンプトでレジストの必要があります(WindowsサービスのExe名が「WCFWinService.exe」の場合)

installutil (フルパス)¥WCFWinService.exe

installutil.exeは.NetFrameworkのインストールされた場所に同梱されています
コンパイルオプションによって,32bit/64bit また,NetFrameworkのバージョンにあわせて,適時準備してくださいませ
(組み合わせが合わない場合,レジスト時,BadImageFormatExceptionな例外発生しまする)

ローカルPC内でプロセス間通信する場合は,これで終わりですけど
外部PCからリモートにてプロセス間通信する場合は,ファイアーウォールを適時設定してください
コマンドプロンプトでやる場合は,こんな感じ(WCFWinService.exeの場合.んで,exeと同フォルダにて実行)
netsh advfirewall firewall add rule name="WCFWinService" dir=in action=allow program="%~dp0WCFWinService.exe" enable=yes protocol=TCP localport=10001

あと,これは片方向のみ.
双方向で通信したい場合(サービス発でクライアントに情報を送りたい)は
これの逆をWindowsサービス側とクライアントに各々実装すれば,いいはず?(やってないんで判らん:爆)

全ソースをアップしておきますーこちらですー
(ポートを開けるバッチも入れておきました)

これでWindowsサービスに対して,やりたい放題できる,はず?
いつもどおり,バグッてたらスマソ❤

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください