仕様10と11の実装
仕様10と11は次のとおりです。
仕様10 「指定した機械語をバイナリ形式で保存できるようにする」
仕様11 「バイナリ形式のファイルは、普通のテキストファイルのように扱えるようにする」
この2つの仕様は、ファイル関連のメニューで実現することになります。
各ファイルメニューの動作は、Wordやメモ帳を参考にします。このように既存の多くのユーザーがいるソフトに操作を合わせておくと、ソフトの習得時間が短くて済むので、メニューについては実務でも既存のソフトの動きを参考にすることが多いです。以上のことを踏まえて実装例の説明に入ります。ファイルメニューの中で一番最初に解説するのは「名前を付けて保存」メニューです。理由はこのメニューを実装しておけば、他のメニューの実装をテストするのに便利だからです。では実装を掲載してからそのコードを説明します。
Private change As Boolean '内容が変化したことを示すフラグ Private fileName As String 'CommandListに追加した機械語の履歴をファイルに保存します Private Sub NamedSaveMenu_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles NamedSaveMenu.Click Try If Me.SaveFile.ShowDialog() = Windows.Forms.DialogResult.OK Then fileName = Me.SaveFile.FileName Dim workFile As FileStream = New FileStream(fileName, FileMode.Create) SaveData(workFile) End If Catch ioe As IOException MessageBox.Show(ioe.Message, "ファイル操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub 'ファイルへ現在のデータを書き込みます Private Sub SaveData(ByRef file As FileStream) Dim writer As BinaryWriter = New BinaryWriter(file) For Each cmd As Command In Me.CommandList.Items writer.Write(cmd.ToBinary()) Next writer.Flush() writer.Close() change = False End Sub
今回の実装は非常に簡単です。大まかな流れは、最初にファイルダイアログを表示し、そのダイアログからファイル名(フルパス)を取得してファイルを保存するだけです。ファイルへデータを書き込む部分についてもCommandが適切なバイナリを返しているので簡単です。ポイントは、FileStreamクラスとBinaryWriterクラスです。この2つのクラスさえ調べたら掲載されたコードはすべて理解できると思います。change変数については次の説明で明らかになりますので気にせず読み進めてください。
では次のメニューを実装します。今度は「閉じる」ファイルメニューを実装します。このメニューでは、現在の作業をすべて取り消し、初期状態に戻します。しかし、初期状態で戻すだけでは不便なので、現在の作業内容を保存するようにします。実装例は次のとおりです。
'変更があれば上書きし、機械語駆動式関数電卓の状態を初期化します。 Private Sub CloseMenu_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CloseMenu.Click '必要があればデータを上書き If change = True Then If MessageBox.Show("データが変更されています。変更を保存しますか?", "確認", _ MessageBoxButtons.YesNoCancel, _ MessageBoxIcon.Information) = Windows.Forms.DialogResult.Yes Then '変更点を保存します Me.SaveMenu_Click(Me.SaveMenu, EventArgs.Empty) End If End If InitApplication() End Sub 'このアプリケーションの状態を初期化します Private Sub InitApplication() Me.InitRegisters() Me.ValueBox.Text = "0" Me.CommandList.Items.Clear() change = False End Sub '各レジスタの初期化を行います Private Sub InitRegisters() Dim cmd As Command cmd = New Command(Me.cpu, CommandName.Mov, RegisterName.EAX, 0UI) Me.cpu.ReadBinary(cmd.ToBinary()) cmd = New Command(Me.cpu, CommandName.Mov, RegisterName.EBX, 0UI) Me.cpu.ReadBinary(cmd.ToBinary()) cmd = New Command(Me.cpu, CommandName.Mov, RegisterName.ECX, 0UI) Me.cpu.ReadBinary(cmd.ToBinary()) cmd = New Command(Me.cpu, CommandName.Mov, RegisterName.EDX, 0UI) Me.cpu.ReadBinary(cmd.ToBinary()) Me.cpu.AnalyzeBinary() Me.cpu.ExecuteCommand() End Sub '機械語駆動式関数電卓を正常に動作させるための下準備を行います Private Sub Calculator_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load '各種変数の初期化を行う cpu = New IntelCpu() Me.InitRegisters() 'ここに処理を追加 'コントロールの準備 RegisterCombo.SelectedIndex = 0 ValueCombo.SelectedIndex = 0 NumberButtons = New List(Of Button)() NumberButtons.AddRange(New Button() {NumberButton0, NumberButton1, _ NumberButton2, NumberButton3, NumberButton4, NumberButton5, _ NumberButton6, NumberButton7, NumberButton8, _ NumberButton9, NumberButtonA, NumberButtonB, _ NumberButtonC, NumberButtonD, NumberButtonE, NumberButtonF}) '10進数を標準にする DecimalOption_CheckedChanged(Nothing, EventArgs.Empty) '各命令系ボタンにコマンド名を設定する Me.AddButton.Tag = CommandName.Add Me.IncButton.Tag = CommandName.Inc Me.SubButton.Tag = CommandName.Subtract Me.DecButton.Tag = CommandName.Dec Me.MulButton.Tag = CommandName.Mul Me.DivButton.Tag = CommandName.Div 'イベントの設定 AddHandler cpu.RegisterValueChanged, AddressOf RegisterValueChanged End Sub
「閉じる」ファイルメニューの実装でポイントになるのは、「このアプリケーションの何が初期状態なのか」です。筆者は電卓の利便性も考慮し、「すべてのレジスタの値が0で、CommandListに何も追加されていない状態」だと考えました。そこで、この実装例では、MOV命令を発行して各レジスタに0を設定するメソッドを追加しました。「閉じる」ファイルメニューでは、この初期化メソッドを間接的に呼び出しています。「閉じる」ファイルメニューはほとんどが初期化処理のみなので説明は以上です。
次は「新規作成」メニューを実装することにします。このメニューの実装は簡単で、特に説明の必要はないと思いますので実装例だけを掲載し次の説明に入ります。
'現在開いているデータファイルを閉じて、新しいデータファイルを作成します。 Private Sub NewMenu_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles NewMenu.Click Try If Me.SaveFile.ShowDialog() = Windows.Forms.DialogResult.OK Then '現在開いているデータファイルを閉じる Me.CloseMenu_Click(Me.CloseMenu, EventArgs.Empty) '新しいファイルに置き換える fileName = Me.SaveFile.FileName Dim workFile As FileStream = New FileStream(fileName, FileMode.Create) SaveData(workFile) End If Catch ioe As IOException MessageBox.Show(ioe.Message, "ファイル操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Error) End Try End Sub
次は「上書き保存」ファイルメニューを実装します。このメニューの実装も簡単なので、コードを掲載してすぐに他のメニューの説明を行います。
'上書き保存をします Private Sub SaveMenu_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SaveMenu.Click If fileName Is Nothing Then 'まだ保存していない場合はファイルを作って保存 Me.NamedSaveMenu_Click(Me.OpenMenu, EventArgs.Empty) Else Dim workFile As FileStream = New FileStream(fileName, FileMode.Create) SaveData(workFile) End If End Sub
残るメニューは「開く」だけです。このメニューの実装は「バイナリファイルを読み出す」処理が必要となりますので、実装例を掲載後その部分を解説します。
'予め保存されているファイルからバイナリを読み込み、CommandListに追加します。
Private Sub OpenMenu_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OpenMenu.Click
'現在開いているファイルを閉じる
If change = True Then
If MessageBox.Show("データが変更されています。変更を保存しますか?", "確認", _
MessageBoxButtons.YesNoCancel, _
MessageBoxIcon.Information) = Windows.Forms.DialogResult.Yes Then
'変更点を保存します
Me.SaveMenu_Click(Me.SaveMenu, EventArgs.Empty)
'次のデータを開くために初期化
Me.InitApplication()
End If
Else
'次のデータを開くために初期化
Me.InitApplication()
End If
Try
If Me.OpenFile.ShowDialog() = Windows.Forms.DialogResult.OK Then
'データの読み込み
fileName = Me.OpenFile.FileName
Dim workFile As FileStream = New FileStream(fileName, FileMode.Open)
Dim reader As BinaryReader = New BinaryReader(workFile)
For i As Long = 0 To workFile.Length
cpu.ReadBinary(reader.ReadBytes(1))
Next
reader.Close()
'機械語命令完了イベントを通じて、ファイルに保存されていた情報を取得する。
cpu.AnalyzeBinary()
AddHandler cpu.ExecuteCommandEnd, AddressOf IntelCpu_ExecuteCommand
cpu.ExecuteCommand()
RemoveHandler cpu.ExecuteCommandEnd, AddressOf IntelCpu_ExecuteCommand
change = False
End If
Catch ioe As IOException
MessageBox.Show(ioe.Message, "ファイル操作エラー", MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
'CommandListへ発行された命令を追加します
Private Sub IntelCpu_ExecuteCommand(ByVal sender As Object, ByVal e As ExecuteCommandEventArgs)
Dim cmd As Command
If e.Source = RegisterName.IMM Then
cmd = New Command(Me.cpu, e.Name, e.Destination, e.Value)
Else
cmd = New Command(Me.cpu, e.Name, e.Destination, e.Source)
End If
Me.CommandList.Items.Add(cmd)
End Sub
Public Class ExecuteCommandEventArgs
Inherits EventArgs
'実行されたコマンドの名前
Private m_name As CommandName
Public Overridable ReadOnly Property Name() As CommandName
Get
Return m_name
End Get
End Property
'処理対象とするレジスタ
Private m_destination As RegisterName
Public Property Destination() As RegisterName
Get
Return Me.m_destination
End Get
Set(ByVal value As RegisterName)
Me.m_destination = value
End Set
End Property
'ソースオペランド
Private m_source As RegisterName
Public Property Source() As RegisterName
Get
Return Me.m_source
End Get
Set(ByVal value As RegisterName)
Me.m_source = value
End Set
End Property
'ソースの値
Private m_value As UInteger
Public Property Value() As UInteger
Get
Return Me.m_value
End Get
Set(ByVal value As UInteger)
Me.m_value = value
End Set
End Property
'必ず実行した命令を指定してインスタンスを生成するようにします
Public Sub New(ByVal name As CommandName)
Me.m_name = name
End Sub
End Class
'解析済みの命令を実行します。
'※以下はIntelCpuクラスのメソッドです。
Public Sub ExecuteCommand()
Dim cmd As OpeCode
While cmds.Count <> 0
cmd = cmds.Dequeue()
Select Case cmd.Name
'命令を選択して実行
Case CommandName.Add
Add(cmd)
Case CommandName.Subtract
Subtract(cmd)
Case CommandName.Inc
Inc(cmd)
Case CommandName.Dec
Dec(cmd)
Case CommandName.Mov
Mov(cmd)
Case CommandName.Mul
Mul(cmd)
Case CommandName.Div
Div(cmd)
End Select
Me.OnExecuteCommandEnd(cmd.Name, cmd.Destination, cmd.Source, cmd.Value.ValueU32)
End While
End Sub
'機械語命令の実行が完了したことを通知する
Private Sub OnExecuteCommandEnd(ByVal name As CommandName, ByVal destination As RegisterName, ByVal source As RegisterName, ByVal value As ULong)
Dim e As ExecuteCommandEventArgs = New ExecuteCommandEventArgs(name)
e.Destination = destination
e.Source = source
e.Value = value
RaiseEvent ExecuteCommandEnd(Me, e)
End Sub
この実装の注目点は「データの読み込み」と「機械語命令完了イベントを通じて、ファイルに保存されていた情報を取得する」の部分です。
データの読み込みは、SaveDataメソッド内で機械語のバイナリ表現を直接書き込み形式で保存していますので、読み出したバイナリそのまま機械語として扱います。機械語のバイナリ表現はIntelCpuクラスが知っていますので、処理はすべてIntelCpuクラスに任せます。しかし一つだけ問題があります。それは、CommandListへ実行した命令を追加しなければならないことです。バイナリを直接CommandListへ追加できませんから、今回はIntelCpuクラスのイベントを利用する形で間接的に追加することにしました。そこでIntelCpuを少し改良し、実行した機械語命令が分かりやすいようにしました。
以上で仕様10・11の実装は終わりです。次は最後に残った2と3を解説します。
