-
同一PC上でプロセス(EXE)間で通信を行いたいことはよくあります。 例えば、1個のPGを何か受信専用として動作させておいて、別のPGではメインの処理を行いながら、たまに受信データの処理を行うなどの要求が出てきたりします。 別EXE同士でやり取りを行う場合には以下の様な方法があると思います。
- WindowsAPIのメッセージ通信
- WindowsAPIのPIPEでの通信
- WindowsAPIのファイルマッピングでの通信
- 通常のファイルを使った通信
- 遅くてOKならば、データベースを介した通信
尚、今回は勉強もかねて Remoting の IPC を使って、クライアントプロセス(EXE)からサーバープロセス(EXE)にメッセージを送信することをやってみます。
今回の例では、1個のソリューションの中に以下の3個のプロジェクトを生成します。- クライアントとサーバーで共有するクラスDLL
- サーバープロセスの実行PG(EXE)
- クライアントプロセスの実行PG(EXE)
先ず、プロセス間通信の準備として、クライアントとサーバーで共有するクラスの生成を行います。
以下のソースを見て下さい。MarshalByRefObject をクラスを継承したクラス ServiceClass を宣言しています。
内容的には多くのことをするわけでは無く、クライアントから呼出されるであろうメソッドで、 文字列データを引数としてイベントを raise しています。
このプロジェクトは IpcService と命名し、クラスライブラリ(DLL)としてビルドしておきます。
(後からこのDLLを、サーバープロセス及びクライアントプロセスのプロジェクトで参照設定します。)クライアントとサーバーで共有するクラス
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に受信したメッセージを表示する為に、 デリゲート処理を宣言しています。
ボタンの押下によりIPCの受信開始を行うのですが、以下の様な手順で行います。- IpcServerChannel によりIPCチャネルを用意し、ChannelServices.RegisterChannel でチャネルを登録
- RemotingConfiguration.RegisterWellKnownServiceType でサービスのタイプを宣言
- RemotingServices.Marshal により共有クラスの参照許可
- IpcChannel.StartListening によりIPCの受信を開始
サーバープロセスPG
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Ipc 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 'IPC受信開始 Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'IPCチャネルを用意 Dim IpcChannel As New IpcServerChannel("ipcPort") ChannelServices.RegisterChannel(IpcChannel, False) Dim strSClassName As String = GetType(ServiceClass).Name RemotingConfiguration.RegisterWellKnownServiceType(GetType(ServiceClass), strSClassName, WellKnownObjectMode.SingleCall) '「ServiceClass」を参照できるように設定 Dim ref As ObjRef = RemotingServices.Marshal(IpcServiceClass, strSClassName) 'IPC受信準備 IpcChannel.StartListening(Nothing) RichTextBox1.AppendText("IPC 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 でIPCチャネルの用意を行います。
ボタン押下時にメッセージ送信処理を行うのですが以下の様な手順で行います。- リモートIPCオブジェクトの URL を定義して Activator.GetObject 参照を取得
- その参照を共有クラスのオブジェクト変数に代入
- 共有参照クラスのイベント生成メソッド呼び出し
このプロジェクトの参照設定もサーバープロセスPGと同様です。
クライアントプロセスPG
Imports System.Runtime.Remoting Imports System.Runtime.Remoting.Channels Imports System.Runtime.Remoting.Channels.Ipc Imports IpcService Public Class frmIpcClient 'IPC準備 Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'IPCチャネルを用意する Dim ipcChannel As New IpcClientChannel ChannelServices.RegisterChannel(ipcChannel, False) End Sub 'メッセージを送信する Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Try '既存のリモートIPCオブジェクトへの参照を取得する Dim strURL As String = "ipc://ipcPort/" + GetType(ServiceClass).Name Dim objRemote As Object = Activator.GetObject(GetType(ServiceClass), strURL) 'サーバとの共通クラスの参照(ServiceClass) Dim objServiceClass As ServiceClass = CType(objRemote, ServiceClass) '共有参照クラスのイベント生成メソッド呼び出し objServiceClass.RaiseServerEvent("Message From Client=>" & Me.TextBox1.Text) Catch ex As Exception MsgBox(ex.Message) End Try End Sub End Class
このソリューションをデバッグ実行するために、ソリューションのプロパティを以下の図の様に設定します。
このソリューションをデバッグ実行すると以下の様になります。
サーバーPGの起動後、「Start IPC Channel」ボタンを押下して IPC 受信の準備を行います。 その後、クライアントPGのボタンを押下するごとに、サーバーPGにメッセージが表示されていきます。
今回のPGはまだまだ不備な点が多いので実用には供しないですが、ご参考になればと思います。
関連する記事
⇒Remoting の IPC を使ったプロセス間通信についてその2(HTTPチャネル)
⇒名前付きパイプを使ったプロセス間通信について
⇒名前付きパイプを使ったプロセス間通信についてその2(複数クライアントとの通信)
⇒名前付きパイプを使ったプロセス間通信についてその3(クライアントとの双方向通信)
PR -
以前、テキストボックスコントロールを基底クラスとして、入力される文字を数字のみとするクラスを紹介しました。
⇒テキストボックスの入力を数字のみにする方法
今回は、入力が便利になる様なプロパティを追加してみたいと思います。
追加する機能は、以下の3個の機能です。- 自分自身にフォーカス移動が在った場合に、背景色を変更する。
- 自分自身にフォーカス移動が在った場合に、テキスト全体を選択状態にする。
- Enter キーによる次のタブストップが指定されているコントロールへのフォーカス移動。
最初の背景色の設定の為に、自分自身がアクティブである時の背景色と、 アクティブではない時の背景色を設定するプロパティを設定します。
クラスの静的変数にアクティブ時(_ActiveBackColor)と非アクティブ時(_InactiveBackColor)の背景色を持ちます。 さらに、この変数をアクセスするためのプロパティ宣言をそれぞれ、ActiveBackColor InactiveBackColor とします。
背景色を変える処理を、Enterイベントの処理の OnEnter 及び、Leaveイベントの処理の OnLeave で処理を行います。
また、フォーカスが移動してきた時の、テキスト全体を選択状態にする指示プロパティを ActiveSelectAll として宣言します。
実際の処理は、 OnEnter 内でこのフラグを見て、 True の場合に、テキストの全選択を行います。
以下にソースを示します。テキストボックス拡張クラスにプロパティを追加する方法
Public Class ClsNumTextBox2 Inherits TextBox 'アクティブ時の背景色 Private _ActiveBackColor As Color = Color.White '非アクティブ時の背景色 Private _InactiveBackColor As Color = Color.White 'アクティブ時の全選択フラグ Private _ActiveSelectAll As Boolean = False 'クリップボード貼付メッセージ Const WM_PASTE As Integer = &H302 '''
''' WndProc メソッド (override) ''' ''' <param name="m">メッセージ</param> Protected Overrides Sub WndProc(ByRef m As Message) Select Case m.Msg Case WM_PASTE Dim iDataObj As IDataObject = Clipboard.GetDataObject() 'クリップボードの中身があるか、かつ文字列に変換できるか?? If (iDataObj IsNot Nothing) And (iDataObj.GetDataPresent(DataFormats.Text) = True) Then '文字列に変換 Dim strClip As String = DirectCast(iDataObj.GetData(DataFormats.Text), String) '文字列が数字のみか調べる If Me.ChkClipString(strClip) = False Then Return End If End If End Select 'メッセージのデフォルト処理を呼出す MyBase.WndProc(m) End Sub '''''' コンストラクタ ''' Public Sub New() MyBase.New() 'IMEを無効にする MyBase.ImeMode = ImeMode.Disable End Sub '''''' アクティブ時の背景色 ''' Public Property ActiveBackColor As Color Set(value As Color) _ActiveBackColor = value '退避値に設定 End Set Get Return _ActiveBackColor '退避値を返す End Get End Property '''''' 非アクティブ時の背景色 ''' Public Property InactiveBackColor As Color Set(value As Color) _InactiveBackColor = value '退避値に設定 End Set Get Return _InactiveBackColor '退避値を返す End Get End Property '''''' アクティブ時の全選択フラグ ''' Public Property ActiveSelectAll As Boolean Set(value As Boolean) _ActiveSelectAll = value '退避値に設定 End Set Get Return _ActiveSelectAll '退避値を返す End Get End Property '''''' Enterイベントの処理 ''' Protected Overrides Sub OnEnter(e As EventArgs) MyBase.OnEnter(e) '背景色の設定 Me.BackColor = Me._ActiveBackColor 'アクティブ時の全選択フラグ If Me._ActiveSelectAll = True Then Me.SelectAll() End If End Sub '''''' Leaveイベントの処理 ''' Protected Overrides Sub OnLeave(e As EventArgs) MyBase.OnLeave(e) '背景色の設定 Me.BackColor = Me._InactiveBackColor End Sub '''''' KeyPressイベントの処理 ''' Protected Overrides Sub OnKeyPress(e As KeyPressEventArgs) MyBase.OnKeyPress(e) 'キーが [0]~[9] または [BackSpace] 以外の場合イベントをキャンセル If Not (("0"c <= e.KeyChar And e.KeyChar <= "9"c) Or e.KeyChar = ControlChars.Back) Then e.Handled = True End If End Sub '''''' 文字列のチェック関数 ''' ''' <param name="strClip">指定文字列</param> '''True:OK, False:NG Private Function ChkClipString(ByVal strClip As String) As Boolean ChkClipString = False If System.Text.RegularExpressions.Regex.IsMatch(strClip, "^[0-9]+$") Then Return True End If End Function '''''' KeyDownイベントの処理 ''' Protected Overrides Sub OnKeyDown(e As KeyEventArgs) MyBase.OnKeyDown(e) '[Enter]キーの場合 If e.KeyCode = Keys.Enter Then If e.Control = False Then '[Ctrl]キーが押下されていない場合、自分の親のコントロールの次のフォーカスへ移動 Me.Parent.SelectNextControl(Me, Not e.Shift, True, True, True) End If End If End Sub End Classこのソースを別のファイルで作成し、一度コンパイルします。 そうすると、ツールボックスの中に ClsNumTextBox2 が現れますので、以下の様にこれをフォーム上に貼り付けます。
ActiveBackColor にはYellow InactiveBackColor にはWhite を設定し、 ActiveSelectAll にはTrue と設定します。このフォームにはコードを何も記述していませんが、そのまま実行した結果は以下の様な感じです。
Enterキーを押下することで、フォーカスが順次移動し、背景が黄色になります。 テキストボックス内に文字列が入力されていれば、フォーカス移動時には選択状態になります。
関連する記事
⇒テキストボックスの入力を数字のみにする方法その2(電卓の様な入力)
⇒テキストボックスの Leave イベントでのエラー処理でフォーカス強制移動する方法について
-
文字列の連結は、簡単な文字列の場合には&(アンド)演算子を用いて行います。 この演算子は二項演算子の様に文字列の連結に使えますし、代入演算子的にも使用できます。 例としては、以下の様な感じです。
Dim strData as String = "ABC" '二項演算子の様に文字列連結 strData = strData & "DEF" '代入演算子の様に文字列連結 strData &= "HIJKLMN"
&(アンド)演算子も使い方が簡単なので、特に速度を必要としない場合にはそのままで特に問題はありません。 この String 型データはそれ自身がオブジェクトであり、中身を変更できない様になっています。 つまり上の二項演算子の例では、「strData」に代入している様な形ですが、
内部的には「strData」の実体は最初の「strData」が持っていた文字列(オブジェクト)と連結したものを 新しいオブジェクトとして作成されます。 では最初の文字列(オブジェクト)はどうなるかと言いますと、システム的には使われないオブジェクトとして 廃棄される状態になります。 実際の廃棄はシステムが勝手に行いますので、心配はいらないのですが、この連結が行われると、どんどん内部的に 使われないオブジェクトが生成されてしまいます。
そこで、この欠点を補うのが StringBuilder クラスです。 StringBuilder は文字列のオブジェクトとしては1個のみ存在し、 そのオブジェクトに対して追加、削除、置換を行いますので上記の様な無駄なオブジェクが生成されずに 処理速度が向上します。
以下の例は、&(アンド)演算子を使った場合と、StringBuilder クラスを使った簡単な例を示します。
文字列の連結の簡単な例
'少ない文字列の連結例 Private Sub BtnStringAdd_Click(sender As Object, e As EventArgs) Handles BtnStringAdd.Click Console.WriteLine("...BtnStringAdd_Click") '===[&]を使った文字列の連結=== Dim strData As String = "" '数値0~9を"0"~"9"として追加連結(10回分の連結) For i As Integer = 0 To 9 strData = strData & i.ToString Next '文字列の左に付加 strData = "文字列:" & strData '表示 Console.WriteLine("[&]の連結:{0}/", strData) '===[StringBuilder]を使った文字列の連結=== Dim sbData As New System.Text.StringBuilder() '数値0~9を"0"~"9"として追加連結 For i As Integer = 0 To 9 sbData.Append(i.ToString) Next '表示 Console.WriteLine("[StringBuilder]の連結:{0}/", sbData.ToString()) End Sub
StringBuilder を使って速度がどの位向上するのかを例として以下に示します。 以下にそのソースを示します。
文字列の連結をStringBuilderで高速に行う
'回数の多い文字列の連結例 Private Sub BtnStringAddBig_Click(sender As Object, e As EventArgs) Handles BtnStringAddBig.Click Console.WriteLine("...BtnStringAddBig_Click") '文字列の宣言 Dim strData As String = "" '開始時刻 Dim tc As Integer = System.Environment.TickCount For i As Integer = 0 To 9 '数値0~9を"0"~"9"として10000回追加 For ii As Integer = 1 To 10000 strData = strData & i.ToString Next Next '処理時間表示(結果文字列は大きすぎるので表示しない) Console.WriteLine("処理時間:{0}ミリ秒", System.Environment.TickCount - tc) '文字列の宣言 Dim sbData As New System.Text.StringBuilder() tc = System.Environment.TickCount For i As Integer = 0 To 9 '数値0~9を"0"~"9"として10000回追加 For ii As Integer = 1 To 10000 sbData.Append(i.ToString) Next Next '処理時間表示(結果文字列は大きすぎるので表示しない) Console.WriteLine("処理時間:{0}ミリ秒", System.Environment.TickCount - tc) End Sub
結果は一目瞭然なのですが、実行時間として100倍ぐらい違います。 処理としては簡単な1万回の連結を10回のループで回していますが、結果的には10万回の文字列連結になりますので &では10万回の String オブジェクト生成が行われるので、時間が掛かる様です。
-
前回は構造体をそのままで読み書きする例を示しましたが、 その中で構造体に配列を持っている場合にはマーシャルの考え方を使うとありましたので、 今回はバイト配列を2個持つ構造体をメモリマップドファイルに書込み、読込む例を示したいと思います。
今回も、メモリマップドファイルの指定で、実際のファイルへの書込みを行いますので、 書込んだ後で、エディタで確認することで、書き込んだデータの値の確認を行います。
以下のソースにその関数を記します。
このフォームは、ボタンを2個配置します。 Button1 は構造体を宣言し、中身を設定してから、構造体をマーシャリングにより、べたなバイト配列として展開します。
そのバイト配列をメモリマップドファイルに書込みを行います。 処理手順は以下の様になります。
1.オブジェクトのアンマネージのバイトサイズでバイト配列宣言
バイト配列を使ってメモリマップドファイルに書込みを行うので、最初に宣言する。
2.アンマネージメモリからメモリを割り当て
アンマネージメモリ(連続したバイト領域としての領域)を(1.)と同じ容量でメモリを確保する。
3.マネージオブジェクトからアンマネージメモリにデータをマーシャリング
構造体の内容を、アンマネージメモリに連続したバイト領域としてコピーする。
4.アンマネージメモリポインターのデータをマネージバイト配列にコピー
アンマネージメモリ(連続したバイト領域としての領域)の内容を、マネージバイト配列に展開する。 (マネージバイト配列はメモリ的には連続した領域に存在するわけでは無く、各配列バイトデータがバラバラに管理されている)
5.アンマネージ メモリから割り当てられたメモリを解放
アンマネージメモリに確保したメモリ領域を解放する。
6.アクセサを使ってメモリマップドファイルへの書込み
マネージバイト配列の内容をメモリマップドファイルへ書込みする。
Button2 はメモリマップドファイルから全体をバイト配列として読込みを行います。 そのバイト配列を、マーシャリングにより構造体に変換する処理を行っています。
処理手順は以下の様になります。
1.オブジェクトのアンマネージのバイトサイズでバイト配列宣言
バイト配列を使ってメモリマップドファイルに書込みを行うので、最初に宣言する。
2.アクセサを使ってメモリマップドファイルから読込
メモリマップドファイルからマネージバイト配列に読込みする。
3.アンマネージメモリからメモリを割り当て
アンマネージメモリ(連続したバイト領域としての領域)を(1.)と同じ容量でメモリを確保する。
4.マネージバイト配列をアンマネージメモリポインターにコピー
メモリマップドファイルから読込まれたマネージバイト配列の内容を、アンマネージメモリ(連続したバイト領域としての領域)に展開する。
5.アンマネージメモリブロックから、指定した型の、新しく割り当てられたマネージオブジェクトにデータをマーシャリング
連続したバイト領域のアンマネージメモリブロックから、指定された型の、マネージな領域にマーシャリングコピーする。
6.アンマネージメモリから割り当てられたメモリを解放
アンマネージメモリに確保したメモリ領域を解放する。共有メモリとしてのメモリマップドファイルの使い方3・配列を持つ構造体の書込と読込
Imports System.IO Imports System.IO.MemoryMappedFiles Imports System.Text Imports System.Runtime.InteropServices Public Class frmMemMapStruc2 '1バイト境界の構造体 <StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Structure StrucTest Const SIZE1 = 8 Const SIZE2 = 16 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=SIZE1)> _ Public arrData1() As Byte 'Byte型配列データ1 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=SIZE2)> _ Public arrData2() As Byte 'Byte型配列データ2 '初期化メソッド Sub New(ByVal intDummy As Integer) Me.arrData1 = New Byte(SIZE1 - 1) {} Me.arrData2 = New Byte(SIZE2 - 1) {} End Sub End Structure 'バイト型配列を文字列に変換する Private Function ConvBytesToString(ByVal pbytDatas() As Byte) As String If pbytDatas(0) = 0 Then Return "" End If 'Trimされる文字(NULL文字、半角空白、全角空白) Dim pchrTrim() As Char = {ControlChars.NullChar, " "c, " "c} 'Shift JISとして文字列に変換 Return System.Text.Encoding.GetEncoding(932).GetString(pbytDatas).Trim(pchrTrim) End Function '文字列をバイト配列に展開する Private Sub ConvStringToBytes(ByVal astrData As String, ByRef abytArr() As Byte) '文字列をShift-JISとしてバイト配列に変換 Dim pbytDatas() As Byte = System.Text.Encoding.GetEncoding(932).GetBytes(astrData) '格納先バイト配列を0x00でクリア Array.Clear(abytArr, 0, abytArr.Length) 'コピーバイト数の調整 Dim pintCopy As Integer = pbytDatas.Length If pintCopy > abytArr.Length Then pintCopy = abytArr.Length End If '格納先にコピー Array.Copy(pbytDatas, abytArr, pintCopy) End Sub 'テキストボックスの内容をメモリマップドファイルに書込む Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'メモリ上にメモリマップドファイルをオープンする Dim mmf = MemoryMappedFile.CreateFromFile("MemMapFileTest.dat", FileMode.OpenOrCreate, "MemMapFileTest", 64) 'メモリマップドファイルのビューに対応するアクセサ生成 Dim acc As MemoryMappedViewAccessor = mmf.CreateViewAccessor() '書込用の構造体の宣言 Dim StrucT As New StrucTest(0) 'ASCII文字列をバイト配列に展開する ConvStringToBytes("1234567", StrucT.arrData1) '8バイト領域に7バイト設定 ConvStringToBytes("ABCDEFGHIJ", StrucT.arrData2) '16バイト領域に10バイト設定 '1.オブジェクトのアンマネージのバイトサイズでバイト配列宣言 Dim size As Integer = Marshal.SizeOf(StrucT) Dim bytes(size) As Byte '2.アンマネージメモリからメモリを割り当て Dim ptr As IntPtr = Marshal.AllocHGlobal(size) '3.マネージオブジェクトからアンマネージメモリにデータをマーシャリング Marshal.StructureToPtr(StrucT, ptr, False) '4.アンマネージ メモリ ポインターのデータを マネージバイト配列にコピー Marshal.Copy(ptr, bytes, 0, size) '5.アンマネージ メモリから割り当てられたメモリを解放 Marshal.FreeHGlobal(ptr) '6.アクセサを使ってメモリマップドファイルへの書込み acc.WriteArray(0, bytes, 0, bytes.Length) 'アクセサ、メモリマップドファイルの廃棄 acc.Dispose() mmf.Dispose() End Sub 'メモリマップドファイルの内容を読込んで表示する Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click 'メモリ上にメモリマップドファイルをオープンする Dim mmf = MemoryMappedFile.CreateFromFile("MemMapFileTest.dat", FileMode.OpenOrCreate, "MemMapFileTest", 64) 'メモリマップドファイルのビューに対応するアクセサ生成 Dim acc As MemoryMappedViewAccessor = mmf.CreateViewAccessor() '読込用の構造体の宣言 Dim StrucT2 As StrucTest = Nothing '1.オブジェクトのアンマネージのバイトサイズでバイト配列宣言 Dim size As Integer = Marshal.SizeOf(StrucT2) Dim bytes(size) As Byte '2.アクセサを使ってメモリマップドファイルから読込 acc.ReadArray(0, bytes, 0, size) '3.アンマネージメモリからメモリを割り当て Dim ptr As IntPtr = Marshal.AllocHGlobal(size) '4.マネージバイト配列をアンマネージメモリポインターにコピー Marshal.Copy(bytes, 0, ptr, size) '5.アンマネージメモリブロックから、指定した型の、新しく割り当てられたマネージオブジェクトにデータをマーシャリング StrucT2 = CType(Marshal.PtrToStructure(ptr, GetType(StrucTest)), StrucTest) '6.アンマネージ メモリから割り当てられたメモリを解放 Marshal.FreeHGlobal(ptr) 'アクセサ、メモリマップドファイルの廃棄 acc.Dispose() mmf.Dispose() 'バイト型配列を文字列に変換する Dim strData As String = "" strData &= "arrData1:" & ConvBytesToString(StrucT2.arrData1) & vbCrLf strData &= "arrData2:" & ConvBytesToString(StrucT2.arrData2) & vbCrLf MsgBox("Read Text:" & vbCrLf & strData) End Sub End Class
MemMapFileTest.dat のファイルの内容をバイナリ表示で行うと以下の様になっています。 (「秀丸エディタ」で表示)
関連する記事
⇒共有メモリとしてのメモリマップドファイルの使い方(MemoryMappedFile,CreateOrOpen,CreateViewAccessor)
⇒共有メモリとしてのメモリマップドファイルの使い方2・構造体の書込と読込
-
メモリマップドファイルを使ったバイト配列の読み書きの記事は前回紹介しましたが、 今回は構造体をそのままで読み書きする例を示します。
尚、今回は、実際のデータファイルを指定する方法で、メモリマップドファイルを使います。 ファイルに書込んだ後で、「秀丸」などのエディタで内容を確認する為です。
以下のソースにその関数を記します。
このフォームは、ボタンを2個と、テキストボックスを1個配置します。 Button1 はテキストボックスの文字列をInteger型データに設定し、メモリマップドファイルへの書込み処理を行い、 Button2 はメモリマップドファイルから読込みを行います。
MemoryMappedFile.CreateFromFile は実際のファイル名とマップ名とメモリ領域のサイズを指定し、メモリマップドファイルのオープンを行います。 その後、MemoryMappedFile.CreateViewAccessor のアクセサを使って、メモリ空間への読み書きを行います。共有メモリとしてのメモリマップドファイルの使い方2・構造体の書込と読込
Imports System.IO Imports System.IO.MemoryMappedFiles Imports System.Runtime.InteropServices Public Class frmMemMapStruc '簡単な構造体 <StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Structure StrucTest Public bytData As Byte 'Byte型データ Public intData As Integer 'Integer型データ Public lngData As Long 'Long型データ Public dblData As Double 'Double型データ Public decData As Decimal 'Decimal型データ End Structure 'テキストボックスの内容をメモリマップドファイルに書込む Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'メモリ上にメモリマップドファイルをオープンする Dim mmf = MemoryMappedFile.CreateFromFile("MemMapFileTest.dat", FileMode.OpenOrCreate, "MemMapFileTest", 1024) 'メモリマップドファイルのビューに対応するアクセサ生成 Dim acc As MemoryMappedViewAccessor = mmf.CreateViewAccessor() '構造体宣言と値設定 Dim StrucT As StrucTest StrucT.bytData = &H55 StrucT.intData = CInt(Me.TextBox1.Text) StrucT.lngData = 1000000L StrucT.dblData = 200.123 StrucT.decData = 1000.456 'アクセサで構造体をメモリマップドファイルへ書込み acc.Write(0, StrucT) 'アクセサ、メモリマップドファイルの廃棄 acc.Dispose() mmf.Dispose() End Sub 'メモリマップドファイルの内容を読込んで表示する Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click 'メモリ上にメモリマップドファイルをオープンする Dim mmf = MemoryMappedFile.CreateFromFile("MemMapFileTest.dat", FileMode.OpenOrCreate, "MemMapFileTest", 1024) 'メモリマップドファイルのビューに対応するアクセサ生成 Dim acc As MemoryMappedViewAccessor = mmf.CreateViewAccessor() 'アクセサで構造体にメモリマップドファイルから読込み Dim StrucT As StrucTest acc.Read(0, StrucT) 'アクセサ、メモリマップドファイルの廃棄 acc.Dispose() mmf.Dispose() '構造体の値を表示 Dim strData As String = "" strData &= "bytData:" & StrucT.bytData.ToString("X2") & vbCrLf strData &= "intData:" & StrucT.intData.ToString & vbCrLf strData &= "lngData:" & StrucT.lngData.ToString & vbCrLf strData &= "dblData:" & StrucT.dblData.ToString & vbCrLf strData &= "decData:" & StrucT.decData.ToString & vbCrLf MsgBox("Read:" & strData) End Sub End Class
MemMapFileTest.dat のファイルの内容をバイナリ表示で行うと以下の様になっています。 (「秀丸エディタ」で表示)
このソースで注意するのは、構造体のデータの読み書き処理が終わったところで、 アクセサ及び、メモリマップドファイルのインスタンスの廃棄を行っているところです。
この処理をしないと、各ボタンクリック処理でのプロセスがデータファイルを捕まえたままになって、 MemoryMappedFile.CreateFromFile のところでエラーが発生します。(ファイルのロックが掛かった様な状態)
以下がその様子を示す画像です。(ボタン1のクリック処理を行い、その後で、ボタン2のクリックを行った時の様子です)
書込み、及び読込みの処理を別々のプロセスに分けた場合に、書込み処理中に読込み処理が行われる可能性があります。 また、その逆も考えられますので、MemoryMappedFile.CreateFromFile でエラーが発生した場合には、その後の処理はしない方がいいと思います。
今回の構造体は単純なInteger型やLong型などの全て値型のデータ型ばかりなので、 配列データは出来ないものかと思って以下の様にソースを変更しました。 (Byte型配列データの部分を追加)Imports System.IO Imports System.IO.MemoryMappedFiles Imports System.Runtime.InteropServices Public Class frmMemMapStruc '簡単な構造体 <StructLayout(LayoutKind.Sequential, Pack:=1)> _ Public Structure StrucTest Public bytData As Byte 'Byte型データ Public intData As Integer 'Integer型データ Public lngData As Long 'Long型データ Public dblData As Double 'Double型データ Public decData As Decimal 'Decimal型データ Public arrData() As Byte 'Byte型配列データ End Structure 'テキストボックスの内容をメモリマップドファイルに書込む Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click 'メモリ上にメモリマップドファイルをオープンする Dim mmf = MemoryMappedFile.CreateFromFile("MemMapFileTest.dat", FileMode.OpenOrCreate, "MemMapFileTest", 1024) 'メモリマップドファイルのビューに対応するアクセサ生成 Dim acc As MemoryMappedViewAccessor = mmf.CreateViewAccessor() '構造体宣言と値設定 Dim StrucT As StrucTest StrucT.bytData = &H55 'StrucT.intData = 100 StrucT.intData = CInt(Me.TextBox1.Text) StrucT.lngData = 1000000L StrucT.dblData = 200.123 StrucT.decData = 1000.456 StrucT.arrData = {&H0, &H11, &H55, &HAA} 'アクセサで構造体をメモリマップドファイルへ書込み acc.Write(0, StrucT) 'アクセサ、メモリマップドファイルの廃棄 acc.Dispose() mmf.Dispose() End Sub 'メモリマップドファイルの内容を読込んで表示する Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click 'メモリ上にメモリマップドファイルをオープンする Dim mmf = MemoryMappedFile.CreateFromFile("MemMapFileTest.dat", FileMode.OpenOrCreate, "MemMapFileTest", 1024) 'メモリマップドファイルのビューに対応するアクセサ生成 Dim acc As MemoryMappedViewAccessor = mmf.CreateViewAccessor() 'アクセサで構造体にメモリマップドファイルから読込み Dim StrucT As StrucTest acc.Read(0, StrucT) 'アクセサ、メモリマップドファイルの廃棄 acc.Dispose() mmf.Dispose() '構造体の値を表示 Dim strData As String = "" strData &= "bytData:" & StrucT.bytData.ToString("X2") & vbCrLf strData &= "intData:" & StrucT.intData.ToString & vbCrLf strData &= "lngData:" & StrucT.lngData.ToString & vbCrLf strData &= "dblData:" & StrucT.dblData.ToString & vbCrLf strData &= "decData:" & StrucT.decData.ToString & vbCrLf MsgBox("Read:" & vbCrLf & strData) End Sub End Class
このソースを実行すると以下の様にVBから叱られてしまいました。
アクセサの構造体の書き込みは、構造体の中に参照データが存在する時にはできない様です。 配列データは構造体の arrData 変数は配列データの実体を持っているわけでは無く、 ポインタ的な参照を持っているだけなので、当然だとは思います。
しかし、参照データを含む構造体の場合にはマーシャルの考え方を使えばできる様ですので、 後日また公開したいと思います。関連する記事
⇒共有メモリとしてのメモリマップドファイルの使い方(MemoryMappedFile,CreateOrOpen,CreateViewAccessor)
⇒共有メモリとしてのメモリマップドファイルの使い方3・配列を持つ構造体の書込と読込