[2018/11/05] 関数の引数がクラスオブジェクトの場合の注意点について (No.67)
[2018/11/02] クラスにイベントを実装する方法について・その2(イベントデータクラスの拡張) (No.66)
[2018/11/01] クラスにイベントを実装する方法について(Event, RaiseEvent, WithEvents) (No.65)
[2018/10/23] Remoting の IPC を使ったプロセス間通信についてその2(HTTPチャネル) (No.59)
-
プログラム上でKEYを持つリスト構造を使う場合に利用するのがコレクションクラスです。 良く使うコレクションクラスには以下の様に3種類のものがあります。
クラス名 KEY操作 Index操作 備考 ArrayList × ○ Indexでの操作しかできないコレクション基本クラス
取り扱うデータ型は Object 型Hashtable ○ × 連想配列的なKEYを持つコレクションクラス
KEY及び、取り扱うデータ型は Object 型SortedList ○ ○ ArrayList と Hashtable の性質を併せ持ったコレクションクラス
Indexは指定されたKEY順にソートされた結果が割り振られます
単なる Object 型の配列として使う場合は ArrayList でもいいのですが、 データ処理をする場合には何がしかのキーがあって、それに紐づくデータがあるものです。 そのためキーが扱える Hashtable SortedList を利用します。
これらのコレクションクラスはデータとして扱えるのは、 Object 型なので結果 データ型としてOKなものは何でもになります。 いろんなサイトで、データとして文字列が扱われているものはあるのですが、今回は少しだけ複雑にするため、 ユーザクラスをデータにもつものを示したいと思います。
以下のソースを見て下さい。ユーザクラスとしてTest を宣言しています。
このクラスを SortedList のデータとして追加等を行います。
このプログラムはモジュールファイルに Sub Main を作成しその中に処理を記述しています。 これをコンパイルする場合はプロジェクト・プロパティでスタートアップオブジェクトとして MdlMainSortList を指定して下さい。
クラスデータをリストする SortedList を宣言した後、 テストクラスのデータを4件生成しながら SortedList に追加を行います。 キーとしては4件とも同一の書式で文字列として処理しています。
その後確認の為に SortedList.Values から順次データを取得して、内容を表示しています。 (この時取得されるのはクラス型なので、いわゆる参照型です。)
さて、内部のデータの変更を行う為にキーを指定しデータを取得します。 ここでクラスのパブリック変数に直接値を入れることで内部データの変更が行われます。 (クラスの参照を SortedList はデータとして持っているだけで、その参照により実体データにアクセスします)
再度 SortedList の中身を全て確認するのですが、今回はIndexを使ってデータを取得しています。SortedListでのクラスデータの使い方
Module MdlMainSortList 'テストクラス Private Class Test Public Key As String 'KEYとなる文字列 Public Name As String '名称の文字列 Public Data As Integer 'あるデータとしてのInteger型データ Public Flag As Boolean 'フラグ 'コンストラクタ Sub New(ByVal aKey As String, ByVal aName As String, ByVal aData As String) Me.Key = aKey Me.Name = aName Me.Data = aData Me.Flag = False End Sub End Class <STAThread()> _ Sub Main() 'クラスデータをリストするSortedList Dim SortTest As New SortedList Dim clsTest As Test '敢えてKEYの順番通りでは無い様に SortedList に追加 clsTest = New Test("KEY004", "NAME004", 444) SortTest.Add("KEY004", clsTest) clsTest = New Test("KEY001", "NAME001", 100) SortTest.Add("KEY001", clsTest) clsTest = New Test("KEY003", "NAME003", 333) SortTest.Add("KEY003", clsTest) clsTest = New Test("KEY002", "NAME002", 222) SortTest.Add("KEY002", clsTest) Debug.Print("**************************") 'SortedList のKEY順番に取り出す For Each clsTest In SortTest.Values '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString) Next '1個のデータをKEYで内容を取り出し、そのクラスの中身を変更する clsTest = SortTest.Item("KEY002") '取り出した clsTest はクラスデータの参照なので、以下の処理でクラスの中身を変更 clsTest.Name = "NAMEaaa" clsTest.Data = 555 clsTest.Flag = True Debug.Print("**************************") 'KEY順番に指標で取り出す For i As Integer = 0 To SortTest.Count - 1 'クラスの参照を取り出す clsTest = CType(SortTest.GetByIndex(i), Test) '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString & " : " & clsTest.Flag.ToString) Next End Sub End Module結果の出力を開発環境の「出力」ウインドウに表示していますが、以下の様になります。
("KEY002"のデータが変更されていることが分かります。)
次に、 SortedList を Hashtable に変えてみた例を示します。 特に SortedList の場合と変わりはありませんが、 Indexを使ってのデータへのアクセスは出来ないので、キーを使うことになります。
ここでキーについての注意ですが、私はキーとしては文字列で設定する様にしています。 例えば扱うデータのキーとして「得意先コード(数値で5桁)」「商品コード(数値で8桁)」の2個があった場合は、 それぞれのコードを先頭ゼロ付きで桁数固定で文字列変換し連結したものをキーとしています。
もし「商品コード」等が文字列(漢字を含まないとする)の場合でしたら、 先頭に空白を補って桁数を揃えて連結します。そうしないと比較がうまくいかないからです。Hashtableでのクラスデータの使い方
Module MdlMainHashList 'テストクラス Private Class Test Public Key As String 'KEYとなる文字列 Public Name As String '名称の文字列 Public Data As Integer 'あるデータとしてのInteger型データ Public Flag As Boolean 'フラグ 'コンストラクタ Sub New(ByVal aKey As String, ByVal aName As String, ByVal aData As String) Me.Key = aKey Me.Name = aName Me.Data = aData Me.Flag = False End Sub End Class <STAThread()> _ Sub Main() 'クラスデータをリストするHashtable Dim HashTest As New Hashtable Dim clsTest As Test '敢えてKEYの順番通りでは無い様に SortedList に追加 clsTest = New Test("KEY004", "NAME004", 444) HashTest.Add("KEY004", clsTest) clsTest = New Test("KEY001", "NAME001", 100) HashTest.Add("KEY001", clsTest) clsTest = New Test("KEY003", "NAME003", 333) HashTest.Add("KEY003", clsTest) clsTest = New Test("KEY002", "NAME002", 222) HashTest.Add("KEY002", clsTest) 'KEY順番に指標で取り出す Debug.Print("**************************") For Each clsTest In HashTest.Values '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString) Next '1このデータをKEYで内容を取り出し、そのクラスの中身を変更する clsTest = HashTest.Item("KEY002") '取り出した clsTest はクラスデータの参照なので、以下の処理でクラスの中身を変更 clsTest.Name = "NAMEaaa" clsTest.Data = 555 clsTest.Flag = True 'KEY順番に指標で取り出す Debug.Print("**************************") For Each clsTest In HashTest.Values '内容を確認 Debug.Print(clsTest.Key & " : " & clsTest.Name & " : " & clsTest.Data.ToString) Next End Sub End Module
PR -
関数の引数が値渡しのクラス変数である場合は注意が必要です。 関数内でクラスの変数を介して、クラス内のデータの書き変えが行われた場合、呼び出した側でそれを参照した時に書き変った値を扱います。
これは、クラス変数は参照型として扱われ、関数にはクラス変数の参照そのものの値が渡されて、 その参照値を使ってクラスの実体(インスタンス)にアクセスを行う為、関数呼び出し側と、関数内で同じインスタンスを処理対象とする為です。
実際のプログラムを見れば一目瞭然なのですが以下の様になります。関数の引数が値型のクラス変数である場合
Module mdlClassPrm ' テスト用クラス Class clsPrmTest ' 内部変数を1個持つ Public intData1 As Integer End Class ' 引数がテストクラスの値渡し Sub PrmTestByVal(ByVal clsP As clsPrmTest) ' クラス内部の変数を1加算 clsP.intData1 += 1 End Sub ' 引数がテストクラスの参照渡し Sub PrmTestByRef(ByRef clsP As clsPrmTest) ' クラス内部の変数を1加算 clsP.intData1 += 1 End Sub Public Sub Main() ' テストクラス生成 Dim clsPrm As New clsPrmTest ' テストクラス変数初期化 clsPrm.intData1 = 0 Console.WriteLine("clsPrm.intData1={0}", clsPrm.intData1) ' 引数がテストクラスの値渡しを呼出す Call PrmTestByVal(clsPrm) Console.WriteLine("clsPrm.intData1={0}", clsPrm.intData1) ' 引数がテストクラスの参照渡しを呼出す Call PrmTestByRef(clsPrm) Console.WriteLine("clsPrm.intData1={0}", clsPrm.intData1) End Sub End Moduleクラス clsPrmTest は内部にパブリックな1個の変数を持つだけの簡単なものです。 関数 PrmTestByVal は引数に ByVal (値渡し)としての引数を持ち、内部の処理はクラス内の変数を+1しています。
このプログラムを実行すると、 PrmTestByVal を実行後の表示が「clsPrm.intData1=1」と1加算されたものになります。 関数内でアクセスされた変数と、関数の呼出し側でアクセスされた変数が同じものを扱っています。
引数が ByVal 指定なので値型変数(Integer, Long 等)の様に値そのものが渡されるので、 クラスの場合もクラスそのものが値として渡されると勘違いしがちです。
しかし、関数に渡される値は、クラスの参照型データ、つまりインスタンスを参照している参照データが渡されて、それを元にインスタンスにアクセスされてしまうからです。 参照値はクラスインスタンスのアドレスが渡されると考えた方が分かりやすいかもしれません。
PrmTestByVal の値渡し引数を参照渡しにした PrmTestByRef を宣言しましたが、 この場合、引数で渡されるのは、クラスインスタンスの参照データの参照が渡されます。
参照の参照を使ってクラスインスタンスにアクセスすると、インスタンスの実体にアクセスできるので、PrmTestByVal と同様の動作となります。 この動きがどうも解せないのですが、参照の参照でも、その内部の変数にアクセスが可能な様です。 (原理的に上手く説明ができませんが...)
この参照の参照を理解する為に、以下の様にプログラムを変更します。
参照データの引数を関数内で書き変える例
Module mdlClassPrm ' テスト用クラス Class clsPrmTest Public intData1 As String End Class ' 引数がテストクラスの値渡し Sub PrmTestByVal(ByVal clsP As clsPrmTest) clsP.intData1 += 1 ' 内部で新しくインスタンス生成 Dim clsPnew As New clsPrmTest clsPnew.intData1 = 100 clsP = clsPnew End Sub ' 引数がテストクラスの参照渡し Sub PrmTestByRef(ByRef clsP As clsPrmTest) clsP.intData1 += 1 ' 内部で新しくインスタンス生成 Dim clsPnew As New clsPrmTest clsPnew.intData1 = 200 clsP = clsPnew End Sub Public Sub Main() ' テストクラス生成 Dim clsPrm As New clsPrmTest ' テストクラス変数初期化 clsPrm.intData1 = 0 Console.WriteLine("clsPrm.intData1={0}", clsPrm.intData1) ' 引数がテストクラスの値渡しを呼出す Call PrmTestByVal(clsPrm) Console.WriteLine("clsPrm.intData1={0}", clsPrm.intData1) ' 引数がテストクラスの参照渡しを呼出す Call PrmTestByRef(clsPrm) Console.WriteLine("clsPrm.intData1={0}", clsPrm.intData1) End Sub End Moduleこれを実行すると、以下の様な表示になります。
clsPrm.intData1=0 clsPrm.intData1=1 clsPrm.intData1=200
PrmTestByRef の中では新しくクラスのインスタンスを生成し、参照渡し引数に代入していますので 変数の中身が新しいインスタンスへの参照に置き換わってしまいます。 そのため、関数呼び出し側に戻って表示を行うと、関数内で行った初期値の「200」となります。
では、メイン関数で最初に生成されたクラスのインスタンスはどうなったのでしょうか?
インスタンス自体はVB.NETが管理するメモリ上に存在するのですが、どこからも利用できない状態になります。
このプログラムでは直ぐに実行が終わるので、問題はありません。 もし直ぐに実行が終わらなくても、まあ、心配しなくても、どこからも参照されなくなったインスタンスは、そのうちシステムの方で片づけてくれます。
(この仕組みを「ガベージコレクション」いわゆる「ゴミ集め」というそうです。)
だからと言って、どんどん参照を書き変えていくことをしない方が良いと思います。関連する記事
⇒関数の戻り値がクラス(オブジェクト)の場合について
⇒クラスにイベントを実装する方法について(Event, RaiseEvent, WithEvents)
-
前回の 「クラスにイベントを実装する方法について(Event, RaiseEvent, WithEvents)」 では 現在値を加算、減算しそれぞれMAX値、MIN値を超えたことをイベントにて通知していました。
このイベントは単に通知を行うだけで、計算結果をどうするのかの指示をクラスのインスタンスがあるフォームからは得られません。
そこで、計算結果をそのままにさせるのか、MAX値、MIN値を超えない様にするのかを、フォームから戻せる様に考えます。 方法としては、イベントの第2引数のイベントデータクラス EventArgs にフラグを持たせるために、クラスを拡張します。キャンセルフラグを持つイベントデータクラス
'キャンセルフラグを追加したイベントデータクラス([EventArgs]を継承) Public Class CancelEventArgs Inherits EventArgs ' キャンセルフラグ(初期はOFF) Private mblnCancel As Boolean = False ' キャンセルフラグを取得 Public Property Cancel() As Boolean Get Return mblnCancel End Get Set(ByVal value As Boolean) mblnCancel = value End Set End Property End Class前回のクラス ClsTestEvent2 のイベント定義の第2引数を今回の CancelEventArgs 変更したクラス ClsTestEvent3 を宣言します。
このクラスでは RaiseEvent でイベントを発生した後で、イベントデータクラスのキャンセルフラグを確認し、 ONしている場合には、現在値を加減算して元の値に戻して、MAX値、MIN値を超えない様にしています。
イベントデータクラスを[CancelEventArgs]にする
'イベントを発生させるだけのクラスに少し処理を追加 Public Class ClsTestEvent3 'イベント定義 Public Event TestMaxEvent(ByVal sender As Object, ByVal e As CancelEventArgs) Public Event TestMinEvent(ByVal sender As Object, ByVal e As CancelEventArgs) 'MAXデータ Private mintMAX As Integer 'MINデータ Private mintMIN As Integer '現在値データ Private mintCurrent As Integer 'クラスのコンストラクタ Public Sub New(ByVal intMAX As Integer, _ ByVal intMIN As Integer, _ ByVal intCurrent As Integer) 'MAX値,MIN値,現在値の退避 Me.mintMAX = intMAX Me.mintMIN = intMIN Me.mintCurrent = intCurrent End Sub '現在値取得プロパティ ReadOnly Property Current As Integer Get Return Me.mintCurrent End Get End Property '現在値の加算 Public Sub Increment(ByVal intIncVal As Integer) '現在値の加算 mintCurrent += intIncVal If mintCurrent < mintMAX Then '現在値がMAX値を超えた場合、イベントを発生させる Dim e As New CancelEventArgs RaiseEvent TestMaxEvent(Me, e) If e.Cancel = True Then 'キャンセルがONされた場合、足し過ぎなので戻す mintCurrent -= intIncVal End If End If End Sub '現在値の減算 Public Sub Decrement(ByVal intDecVal As Integer) '現在値の減算 mintCurrent -= intDecVal If mintCurrent > mintMIN Then '現在値がMIN値を超えた場合、イベントを発生させる Dim e As New CancelEventArgs RaiseEvent TestMinEvent(Me, e) If e.Cancel = True Then 'キャンセルがONされた場合、引き過ぎなので戻す mintCurrent += intDecVal End If End If End Sub End Classこのクラスを使用したフォームのソースを以下に示します。
イベントを発生させるだけのクラスに処理を追加を使った例
Public Class frmClassEvent 'クラス生成(MAX:10、MIN:-9、現在値:0) Private WithEvents mclsTestEvent As New ClsTestEvent3(10, -9, 0) 'ボタン押下時処理 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '加算関数コール(2を加算) mclsTestEvent.Increment(2) '加算結果を表示 Me.Label1.Text = "加算結果=" & mclsTestEvent.Current.ToString End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click '減算関数コール(2を減算) mclsTestEvent.Decrement(2) '減算結果を表示 Me.Label1.Text = "減算結果=" & mclsTestEvent.Current.ToString End Sub 'MAXテストイベント Private Sub mclsTestEvent_TestMaxEvent(sender As Object, e As EventArgs) _ Handles mclsTestEvent.TestMaxEvent If MsgBox("MAX値がオーバーしています。加算を中止しますか?", _ MsgBoxStyle.Question + MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then e.Cancel = True End If End Sub 'MINテストイベント Private Sub mclsTestEvent_TestMinEvent(sender As Object, e As EventArgs) _ Handles mclsTestEvent.TestMinEvent If MsgBox("MIN値がオーバーしています。減算を中止しますか?", _ MsgBoxStyle.Question + MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then e.Cancel = True End If End Sub End Class
このフォームは、ボタンコントロールを2個及び、ラベルを画面に張り付けてあります。 Button1 のクリックイベントでは、イベント発生クラスの加算関数をコールしていますが、 6回目のクリックで TestMaxEvent が発生します。
このイベントの処理では、「MAX値がオーバーしています。加算を中止しますか?」を MsgBox で表示し、 「はい」ボタンが押下された場合に、イベントデータのキャンセルフラグをONします。 そうすることで、MAX値を超えて現在値が加算されません。
また、減算側のボタンクリックでも同様の処理を行います。 TestMinEvent の処理では、「MIN値がオーバーしています。減算を中止しますか?」を MsgBox で表示し、 「はい」ボタンが押下された場合に、イベントデータのキャンセルフラグをONします。結果、MIN値を超えて現在値が減算されません。
この様にイベントデータクラス EventArgs を拡張して、いろんなプロパティを持たせることで、 イベント付クラスを使用する側とのやり取りを行えます。関連する記事
⇒クラスにイベントを実装する方法について(Event, RaiseEvent, WithEvents)
-
クラスにイベントを発生させる機能を持たせる方法について説明します。
クラスを使う側からクラスのイベント等を使用した後で、使用側へのいろんな通知をイベントを通して行えます。
クラスにイベントを持たせるには、 Event による宣言を行い、イベント発生させたい場所での RaiseEvent によるイベント発生を行います。
先ずはイベント処理のみをテストするためのクラス宣言です。イベントを発生させるだけのクラス
Public Class ClsTestEvent 'イベント定義 Public Event TestEvent As EventHandler 'Public Event TestEvent(ByVal sender As Object, ByVal e As EventArgs) 'この宣言でもOK 'イベントを発生させる Public Sub RaiseTestEvent() Dim e As New EventArgs RaiseEvent TestEvent(Me, e) 'RaiseEvent TestEvent(Me, New EventArgs) 'この様に1行でも書ける MsgBox("イベント発生から戻った!") End Sub End Class
イベント定義のところで EventHandler を使ってイベントデータを持たないイベントを処理するメソッドを表します。
この定義ですが、「この宣言でもOK」と書かれた行の書き方でも同じ宣言になります。
このクラスを使った例を以下に示します。
クラス変数宣言で WithEvents を付加することで Hanldes を使ったイベントプロシージャを生成できます。 (Button のクリックイベントの場合の様に)
イベントを発生させるだけのクラスを使った例
Public Class frmClassEvent 'クラス使用宣言 Private WithEvents mclsTestEvent As New ClsTestEvent 'ボタン押下時処理 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'イベント起動関数コール mclsTestEvent.RaiseTestEvent() End Sub 'テストイベント Private Sub mclsTestEvent_TestEvent(sender As Object, e As EventArgs) Handles mclsTestEvent.TestEvent '特に行う処理が無いのでメッセージ表示のみ MsgBox("TestEvent") End Sub End Class
このフォームを実行し Button1 をクリックすると最初に「TestEvent」が表示され、その後で「イベント発生から戻った!」が表示されます。 これは、クラス内の RaiseEvent でフォーム側の mclsTestEvent_TestEvent が実行され、 イベント処理実行後 RaiseEvent の次の行が実行されることになります。
このことは、イベント処理の後で何か処理が必要であればこれ以降に行えばよいことになります。 例とすれば、イベント処理からキャンセル指示などを返してキャンセルの処理を行わせるなどです。
上の例ではクラス内で特に処理を行っていなかったので、少し簡単な処理を追加したクラスを定義します。
クラスの処理としては数値の加算、減算を行うものを考えます。- イベント定義として最大値を超えた場合と、最小値を超えた場合のイベントを宣言する。
- クラス内部に現在値、最大値、最小値の変数を宣言する。
- クラスのコンストラクタ New で現在値、最大値、最小値の初期設定を行う。
- 現在値の取得プロパティとして Current を宣言する。
- 現在値に指定値を加算する関数を定義し、最大値を超えた場合にイベントを発生する。
- 現在値に指定値を減算する関数を定義し、最小値を超えた場合にイベントを発生する。
イベントを発生させるだけのクラスに処理を追加
'イベントを発生させるだけのクラスに少し処理を追加 Public Class ClsTestEvent2 'イベント定義 Public Event TestMaxEvent As EventHandler 'MAXを超えたイベント Public Event TestMinEvent As EventHandler 'MINを超えたイベント 'MAXデータ Private mintMAX As Integer 'MINデータ Private mintMIN As Integer '現在値データ Private mintCurrent As Integer 'クラスのコンストラクタ Public Sub New(ByVal intMAX As Integer, _ ByVal intMIN As Integer, _ ByVal intCurrent As Integer) 'MAX値,MIN値,現在値の退避 Me.mintMAX = intMAX Me.mintMIN = intMIN Me.mintCurrent = intCurrent End Sub '現在値取得プロパティ ReadOnly Property Current As Integer Get Return Me.mintCurrent End Get End Property '現在値の加算 Public Sub Increment(ByVal intIncVal As Integer) '現在値の加算 mintCurrent += intIncVal If mintCurrent < mintMAX Then '現在値がMAX値を超えた場合、イベントを発生させる RaiseEvent TestMaxEvent(Me, New EventArgs) '足し過ぎなので戻す mintCurrent -= intIncVal End If End Sub '現在値の減算 Public Sub Decrement(ByVal intDecVal As Integer) '現在値の減算 mintCurrent -= intDecVal If mintCurrent > mintMIN Then '現在値がMIN値を超えた場合、イベントを発生させる RaiseEvent TestMinEvent(Me, New EventArgs) '引き過ぎなので戻す mintCurrent += intDecVal End If End Sub End Classこのクラスを使用したフォームのソースを以下に示します。
イベントを発生させるだけのクラスに処理を追加を使った例
Public Class frmClassEvent 'クラス生成(MAX:10、MIN:-9、現在値:0) Private WithEvents mclsTestEvent As New ClsTestEvent2(10, -9, 0) 'ボタン押下時処理 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '加算関数コール(2を加算) mclsTestEvent.Increment(2) '加算結果を表示 Me.Label1.Text = "加算結果=" & mclsTestEvent.Current.ToString End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click '減算関数コール(2を減算) mclsTestEvent.Decrement(2) '減算結果を表示 Me.Label1.Text = "減算結果=" & mclsTestEvent.Current.ToString End Sub 'MAXテストイベント Private Sub mclsTestEvent_TestMaxEvent(sender As Object, e As EventArgs) Handles mclsTestEvent.TestMaxEvent MsgBox("MaxOver!!") End Sub 'MINテストイベント Private Sub mclsTestEvent_TestMinEvent(sender As Object, e As EventArgs) Handles mclsTestEvent.TestMinEvent MsgBox("MinOver!!") End Sub End Class
このフォームは、ボタンコントロールを2個及び、ラベルを画面に張り付けてあります。 Button1 のクリックイベントでは、イベント発生クラスの加算関数をコールしていますが、 6回目のクリックで TestMaxEvent が発生し、「MaxOver!!」が表示されます。
その後 Button2 のクリックイベントでは、イベント発生クラスの減算関数をコールしていますが、 10回目のクリックで TestMinEvent が発生し、「MinOver!!」が表示されます。
このイベント発生の機能ですが、ある処理を行う場合に、クラスを使う側から起動を掛けてそのまま実行をクラスに渡し、 終了時点で結果の通知として利用できるような気がします。いろいろ使い道が有りそうな感じです。関連する記事
⇒SerialPortコントロールの使い方その4(データ受信時にイベントを発生させる)
⇒クラスにイベントを実装する方法について・その2(イベントデータクラスの拡張)
おすすめ本
-
前回投稿した以下の記事ですが、プロセス間通信の方法としてRemoting の IPCを使用していました。
⇒Remoting の IPC を使ったプロセス間通信について
今回は、HTTPチャネルを介してのプロセス間通信に変更してみます。 前回同様、1個のソリューショの中に以下の3個のプロジェクトを設置します。- クライアントとサーバーで共有するクラスDLL
- サーバープロセスの実行PG(EXE)
- クライアントプロセスの実行PG(EXE)
先ず、プロセス間通信の準備として、クライアントとサーバーで共有するクラスの宣言ですがこれは前回と全く同じものです。クライアントとサーバーで共有するクラス
Public Class ServiceClass Inherits MarshalByRefObject 'クライアントから呼び出しを受け、イベントを発生させる Public Sub RaiseServerEvent(ByVal message As String) 'イベントを発生させる RaiseEvent RaiseClientEvent(message) End Sub Public Event RaiseClientEvent(ByVal messsage As String) End Classプロジェクトの定義は以下の様になります。
次に、サーバープロセスPGのプロジェクトを生成します。
1個のフォーム frmIpcServer をプロジェクトに追加し、そのフォーム上に1個のボタンとリッチエディットBOXを設置します。
フォームの静的変数として上記で説明した共有クラス(ServiceClass)の生成を行います。 また、ServiceClass から raise されるイベントの処理として、リッチエディットBOXに受信したメッセージを表示する為に、 デリゲート処理を宣言しています。
ボタンの押下によりHTTPの受信開始を行うのですが、以下の様な手順で行います。- HttpChannelによりHTTPチャネルを用意し、ChannelServices.RegisterChannel でチャネルを登録
- RemotingServices.Marshal により共有クラスの参照許可
- httpChannel.StartListening によりHTTPの受信を開始
サーバープロセスPG
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Http Imports IpcService Public Class frmIpcServer 'IPC用クラスの生成 Private WithEvents IpcServiceClass As New IpcService.ServiceClass '受信回数 Private mintRcv As Integer = 0 '別プロセスからのメッセージを処理するためデリゲートを利用 Delegate Sub SetRichTextBox1Delegate(ByVal Value As String) Private RichTextBox1Delegate As New SetRichTextBox1Delegate(AddressOf AppendTextRichTextBox1) 'リッチテキストボックスにメッセージを表示する Private Sub AppendTextRichTextBox1(ByVal message As String) RichTextBox1.AppendText(message) End Sub '受信準備 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'HTTPチャネルを用意(ポートは8080を利用) Dim httpChannel = New HttpChannel(8080) ChannelServices.RegisterChannel(httpChannel, False) '「IpcServiceClass」を「ipcsample」で参照できるように設定 Dim ref As ObjRef = RemotingServices.Marshal(IpcServiceClass, "ipcsample") 'HTTP受信準備 httpChannel.StartListening(Nothing) RichTextBox1.AppendText("HTTP channel is ready to receive." & vbNewLine) End Sub 'IPC用クラスのイベント処理 Private Sub IpcServiceClass_RaiseClientEvent(ByVal message As String) Handles IpcServiceClass.RaiseClientEvent 'このイベント処理は、別プロセスのServiceClassからRaiseされるので、 'このフォームのコントロールにアクセスするにはデリゲート処理を行う '(クライアントから受信したメッセージを処理) mintRcv += 1 Me.Invoke(RichTextBox1Delegate, New Object() {mintRcv.ToString & ":" & message & vbNewLine}) End Sub End Class尚、このプロジェクトの参照設定で以下のものを追加します。
- System.Runtime.Remoting
- IpcService
最後に、クライアントプロセスPGのプロジェクトを生成します。
1個のフォーム frmIpcClient をプロジェクトに追加し、そのフォーム上に1個のボタンとディットBOXを設置します。
フォームロード時に ChannelServices.RegisterChannel でHTTPチャネルの用意を行い、 RemotingConfiguration.RegisterWellKnownClientType でリモートサーバへの接続準備を行います。
ボタン押下時に IPC用クラスの生成を行い、そのクラスのイベント発生メソッドを実行し、メッセージ送信処理を行います。
尚、このプロジェクトの参照設定もサーバープロセスPGと同様です。クライアントプロセスPG
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Http Imports IpcService Public Class frmIpcClient 'IPC準備 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'HTTPチャネルを用意する Dim channel As New HttpChannel(0) ChannelServices.RegisterChannel(channel, False) 'リモートサーバへの接続準備 RemotingConfiguration.RegisterWellKnownClientType(GetType(ServiceClass), "http://localhost:8080/ipcsample") End Sub 'メッセージを送信する Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Try 'IPC用クラスの生成 Dim IpcServiceClass As ServiceClass = New ServiceClass() 'サーバへの通知(引数には、送信したいメッセージを指定) IpcServiceClass.RaiseServerEvent("Message From Client=>" & Me.TextBox1.Text) Catch ex As Exception MsgBox(ex.Message) End Try End Sub End Classこのソリューションをデバッグ実行するために、ソリューションのプロパティを以下の図の様に設定します。
このソリューションをデバッグ実行すると以下の様になります。
サーバーPGの起動後、「Start HTTP Channel」ボタンを押下して HTTP 受信の準備を行います。 その後、クライアントPGのボタンを押下するごとに、サーバーPGにメッセージが表示されていきます。
関連する記事
⇒Remoting の IPC を使ったプロセス間通信について
⇒名前付きパイプを使ったプロセス間通信について
⇒名前付きパイプを使ったプロセス間通信についてその2(複数クライアントとの通信)
⇒名前付きパイプを使ったプロセス間通信についてその3(クライアントとの双方向通信)