SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

VB.NETで学ぶ機械語の基礎

VB.NETで仮想CPUを作ろう(14)
- 機械語駆動式 関数電卓を作ろう! 解答編(後半)

VB.NETで学ぶ機械語の基礎 第14回

ダウンロード VirtualCPU14.zip (475.5 KB)

仕様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を解説します。

次のページ
仕様2と3の実装

この記事は参考になりましたか?

VB.NETで学ぶ機械語の基礎連載記事一覧

もっと読む

この記事の著者

インドリ(インドリ)

分析・設計・実装なんでもありのフリーエンジニア。ブログ「無差別に技術をついばむ鳥(http://indori.blog32.fc2.com/)」の作者です。アドバイザーをしたり、システム開発したり、情報処理技術を研究したりと色々しています。座右の銘は温故知新で、新旧関係なく必要だと考えたものは全て学...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/3425 2010/04/27 12:04

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー