(Excel VBA)SHIFT‐JISでエンコードされたバイナリデータファイルを作成するコードについて - tsukimisoba/Blog GitHub Wiki

2025/06/20

SHIFT-JISでエンコードされたバイナリデータファイルを作成するコードについて

' PersonDataを表現する構造体
Public Type PersonData
    Name As String
    Age As Long
End Type

' バイナリファイル処理用のモジュール
Public Module BinaryFileHandler

    ' 定数定義
    Private Const NAME_SIZE As Integer = 10
    Private Const AGE_SIZE As Integer = 4
    Private Const RECORD_SIZE As Integer = NAME_SIZE + AGE_SIZE

    ' Excel VBAでのバイナリファイル書き込み
    Public Sub WritePersonsToFile(filePath As String, persons() As PersonData)
        Dim fileNum As Integer
        Dim i As Integer
        
        ' ファイル番号を取得
        fileNum = FreeFile
        
        ' バイナリモードでファイルを開く
        Open filePath For Binary Access Write As #fileNum
        
        ' 各レコードを書き込み
        For i = LBound(persons) To UBound(persons)
            Call WritePersonRecord(fileNum, persons(i))
        Next i
        
        ' ファイルを閉じる
        Close #fileNum
    End Sub
    
    ' バイナリファイルから読み込み
    Public Function ReadPersonsFromFile(filePath As String) As PersonData()
        Dim fileNum As Integer
        Dim fileSize As Long
        Dim recordCount As Long
        Dim persons() As PersonData
        Dim i As Long
        
        ' ファイル番号を取得
        fileNum = FreeFile
        
        ' バイナリモードでファイルを開く
        Open filePath For Binary Access Read As #fileNum
        
        ' ファイルサイズを取得
        fileSize = LOF(fileNum)
        
        ' レコード数を計算
        recordCount = fileSize / RECORD_SIZE
        
        ' 配列を初期化
        If recordCount > 0 Then
            ReDim persons(1 To recordCount)
            
            ' 各レコードを読み込み
            For i = 1 To recordCount
                persons(i) = ReadPersonRecord(fileNum)
            Next i
        Else
            ReDim persons(1 To 0) ' 空の配列
        End If
        
        ' ファイルを閉じる
        Close #fileNum
        
        ReadPersonsFromFile = persons
    End Function
    
    ' 1レコード分のデータを書き込み
    Private Sub WritePersonRecord(fileNum As Integer, person As PersonData)
        Dim nameBytes() As Byte
        Dim ageBytes() As Byte
        
        ' 氏名を固定長バイト配列に変換
        nameBytes = GetFixedLengthNameBytes(person.Name)
        
        ' 年齢をバイト配列に変換(リトルエンディアン)
        ageBytes = LongToBytes(person.Age)
        
        ' バイナリデータを書き込み
        Put #fileNum, , nameBytes
        Put #fileNum, , ageBytes
    End Sub
    
    ' 1レコード分のデータを読み込み
    Private Function ReadPersonRecord(fileNum As Integer) As PersonData
        Dim person As PersonData
        Dim nameBytes(1 To NAME_SIZE) As Byte
        Dim ageBytes(1 To AGE_SIZE) As Byte
        
        ' 氏名部分を読み込み
        Get #fileNum, , nameBytes
        person.Name = GetStringFromBytes(nameBytes)
        
        ' 年齢部分を読み込み
        Get #fileNum, , ageBytes
        person.Age = BytesToLong(ageBytes)
        
        ReadPersonRecord = person
    End Function
    
    ' 文字列を固定長SHIFT-JISバイト配列に変換
    Private Function GetFixedLengthNameBytes(text As String) As Byte()
        Dim result(1 To NAME_SIZE) As Byte
        Dim textBytes() As Byte
        Dim i As Integer
        Dim copyLength As Integer
        
        ' 配列を0で初期化
        For i = 1 To NAME_SIZE
            result(i) = 0
        Next i
        
        If Len(text) > 0 Then
            ' SHIFT-JISバイト配列に変換
            textBytes = StrConv(text, vbFromUnicode)
            
            ' コピーする長さを決定(最大NAME_SIZE)
            copyLength = UBound(textBytes) - LBound(textBytes) + 1
            If copyLength > NAME_SIZE Then
                copyLength = NAME_SIZE
            End If
            
            ' バイトをコピー
            For i = 1 To copyLength
                result(i) = textBytes(i - 1 + LBound(textBytes))
            Next i
        End If
        
        GetFixedLengthNameBytes = result
    End Function
    
    ' SHIFT-JISバイト配列から文字列に変換
    Private Function GetStringFromBytes(bytes() As Byte) As String
        Dim i As Integer
        Dim endPos As Integer
        Dim validBytes() As Byte
        
        ' 0バイトまでの位置を検索
        endPos = 0
        For i = LBound(bytes) To UBound(bytes)
            If bytes(i) = 0 Then
                Exit For
            End If
            endPos = endPos + 1
        Next i
        
        If endPos = 0 Then
            GetStringFromBytes = ""
            Exit Function
        End If
        
        ' 有効なバイト部分だけを抽出
        ReDim validBytes(0 To endPos - 1)
        For i = 0 To endPos - 1
            validBytes(i) = bytes(i + LBound(bytes))
        Next i
        
        ' SHIFT-JISからUnicodeに変換
        GetStringFromBytes = StrConv(validBytes, vbUnicode)
    End Function
    
    ' Long値をバイト配列に変換(リトルエンディアン)
    Private Function LongToBytes(value As Long) As Byte()
        Dim result(1 To 4) As Byte
        
        result(1) = value And &HFF
        result(2) = (value \ &H100) And &HFF
        result(3) = (value \ &H10000) And &HFF
        result(4) = (value \ &H1000000) And &HFF
        
        LongToBytes = result
    End Function
    
    ' バイト配列をLong値に変換(リトルエンディアン)
    Private Function BytesToLong(bytes() As Byte) As Long
        Dim result As Long
        
        result = bytes(LBound(bytes)) + _
                 bytes(LBound(bytes) + 1) * &H100& + _
                 bytes(LBound(bytes) + 2) * &H10000 + _
                 bytes(LBound(bytes) + 3) * &H1000000
        
        BytesToLong = result
    End Function

End Module

' 使用例とテスト用のサブルーチン
Sub TestBinaryFileHandling()
    Dim persons(1 To 3) As PersonData
    Dim readPersons() As PersonData
    Dim filePath As String
    Dim i As Integer
    
    ' テストデータを作成
    persons(1).Name = "田中太郎"
    persons(1).Age = 25
    
    persons(2).Name = "佐藤花子"
    persons(2).Age = 30
    
    persons(3).Name = "山田次郎"
    persons(3).Age = 35
    
    ' ファイルパスを設定(Excelファイルと同じフォルダ)
    filePath = ThisWorkbook.Path & "\persons.dat"
    
    ' ファイルに書き込み
    Debug.Print "データをファイルに書き込み中..."
    Call WritePersonsToFile(filePath, persons)
    Debug.Print "書き込み完了"
    
    ' ファイルから読み込み
    Debug.Print "ファイルからデータを読み込み中..."
    readPersons = ReadPersonsFromFile(filePath)
    Debug.Print "読み込み完了"
    
    ' 読み込んだデータを表示
    Debug.Print "読み込んだデータ:"
    For i = LBound(readPersons) To UBound(readPersons)
        Debug.Print "氏名: " & readPersons(i).Name & ", 年齢: " & readPersons(i).Age
    Next i
    
    ' ワークシートにも出力
    Call OutputToWorksheet(readPersons)
End Sub

' ワークシートにデータを出力
Sub OutputToWorksheet(persons() As PersonData)
    Dim ws As Worksheet
    Dim i As Integer
    
    ' アクティブなワークシートを取得
    Set ws = ActiveSheet
    
    ' ヘッダーを設定
    ws.Cells(1, 1).Value = "氏名"
    ws.Cells(1, 2).Value = "年齢"
    
    ' データを出力
    For i = LBound(persons) To UBound(persons)
        ws.Cells(i - LBound(persons) + 2, 1).Value = persons(i).Name
        ws.Cells(i - LBound(persons) + 2, 2).Value = persons(i).Age
    Next i
End Sub