VBAでHKEY_CURRENT_USERレジストリを操作する Win32 API活用ガイド

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

VBAでHKEY_CURRENT_USERレジストリを操作する Win32 API活用ガイド

背景と要件

Officeアプリケーション(Excel, Accessなど)で複雑な業務自動化を行う際、ユーザー固有の設定やアプリケーションの状態を永続化したいというニーズがしばしば発生します。ファイルへの保存も可能ですが、レジストリはOSレベルで管理され、ユーザープロファイルに紐付くため、特に単一ユーザー環境での設定管理に適しています。VBAにはレジストリを直接操作する組み込み関数がありませんが、Windows API (Win32 API) を利用することで、HKEY_CURRENT_USER以下のレジストリキーや値を安全かつ確実に操作できます。 、VBAからWin32 APIをDeclare PtrSafeで宣言し、HKEY_CURRENT_USER下のレジストリを読み書き、作成、削除するための実務レベルのコードを提供します。外部ライブラリは一切使用せず、Windows OSが標準で提供する機能のみを利用します。ExcelおよびAccessを対象とし、再現性の高いコードと、性能チューニング、運用上の注意点についても詳述します。

設計

VBAでレジストリを操作するためには、以下のWin32 API関数を使用します。これらはadvapi32.dllライブラリに含まれています。

  • RegOpenKeyExA: 既存のレジストリキーを開く。

  • RegCreateKeyExA: レジストリキーを作成または開く。

  • RegSetValueExA: 指定されたレジストリキーに値を設定する(文字列、DWORDなど)。

  • RegQueryValueExA: 指定されたレジストリキーの値を読み出す。

  • RegDeleteValueA: 指定されたレジストリキーから値を削除する。

  • RegDeleteKeyA: 指定されたレジストリキーを削除する。

  • RegCloseKey: 開いたレジストリキーのハンドルを閉じる。

これらのAPIをVBAから呼び出す際には、64bit環境への対応としてDeclare PtrSafeキーワードと、ポインタやハンドルを格納するためのLongPtrデータ型を使用します[1]。

エラーハンドリングはレジストリ操作において不可欠です。各API関数は操作の成否を示す戻り値を返します(0は成功、0以外はエラーコード)。この戻り値を適切にチェックすることで、予期せぬ問題に対処し、安定したアプリケーションを構築します。

レジストリ操作フロー

以下のMermaid図は、VBAにおける一般的なレジストリ操作のフローを示しています。

flowchart LR
    Start["処理開始"] --> A{"レジストリキーが存在するか?"};
    A -- はい |RegOpenKeyExA| --> B["レジストリキーを開く"];
    A -- いいえ |RegCreateKeyExA| --> C["レジストリキーを作成"];
    B --> D["レジストリ値の設定/更新"];
    C --> D;
    D -- 文字列/DWORD値設定 |RegSetValueExA| --> E["レジストリ値の読み出し"];
    E -- 値取得 |RegQueryValueExA| --> F["レジストリ値の検証/利用"];
    F --> G{"後処理が必要か?"};
    G -- はい(値削除) |RegDeleteValueA| --> H["レジストリ値の削除"];
    G -- はい(キー削除) |RegDeleteKeyA| --> I["レジストリキーの削除"];
    H --> J["レジストリハンドルを閉じる"];
    I --> J;
    J -- 処理完了 |RegCloseKey| --> End["処理終了"];

実装

以下のコードは、ExcelおよびAccessの標準モジュールに記述することで利用できます。HKEY_CURRENT_USER配下に任意のキーを作成し、値を書き込み、読み出し、そして削除する一連の処理をWin32 APIを通じて行います。

1. Win32 APIの宣言 (共通モジュール)

advapi32.dllから必要なAPI関数をDeclare PtrSafeで宣言します。

' 標準モジュール (例: Module1) に記述

' --- Win32 API 定数と宣言 ---
#If VBA7 Then

    ' VBA7 (Office 2010以降, 64bit/32bit対応)
    Private Declare PtrSafe Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" ( _
        ByVal hKey As LongPtr, _
        ByVal lpSubKey As String, _
        ByVal ulOptions As Long, _
        ByVal samDesired As Long, _
        ByRef phkResult As LongPtr) As Long

    Private Declare PtrSafe Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" ( _
        ByVal hKey As LongPtr, _
        ByVal lpSubKey As String, _
        ByVal Reserved As Long, _
        ByVal lpClass As String, _
        ByVal dwOptions As Long, _
        ByVal samDesired As Long, _
        ByVal lpSecurityAttributes As LongPtr, _
        ByRef phkResult As LongPtr, _
        ByRef lpdwDisposition As LongPtr) As Long

    Private Declare PtrSafe Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" ( _
        ByVal hKey As LongPtr, _
        ByVal lpValueName As String, _
        ByVal Reserved As Long, _
        ByVal dwType As Long, _
        ByVal lpData As LongPtr, _
        ByVal cbData As Long) As Long

    Private Declare PtrSafe Function RegQueryValueExString Lib "advapi32.dll" Alias "RegQueryValueExA" ( _
        ByVal hKey As LongPtr, _
        ByVal lpValueName As String, _
        ByVal lpReserved As Long, _
        ByRef lpType As Long, _
        ByVal lpData As String, _
        ByRef lpcbData As Long) As Long

    Private Declare PtrSafe Function RegQueryValueExLong Lib "advapi32.dll" Alias "RegQueryValueExA" ( _
        ByVal hKey As LongPtr, _
        ByVal lpValueName As String, _
        ByVal lpReserved As Long, _
        ByRef lpType As Long, _
        ByRef lpData As Long, _
        ByRef lpcbData As Long) As Long

    Private Declare PtrSafe Function RegCloseKey Lib "advapi32.dll" ( _
        ByVal hKey As LongPtr) As Long

    Private Declare PtrSafe Function RegDeleteValue Lib "advapi32.dll" Alias "RegDeleteValueA" ( _
        ByVal hKey As LongPtr, _
        ByVal lpValueName As String) As Long

    Private Declare PtrSafe Function RegDeleteKey Lib "advapi32.dll" Alias "RegDeleteKeyA" ( _
        ByVal hKey As LongPtr, _
        ByVal lpSubKey As String) As Long
#Else

    ' VBA6以前 (Office 2007以前, 32bitのみ)
    Private Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" ( _
        ByVal hKey As Long, _
        ByVal lpSubKey As String, _
        ByVal ulOptions As Long, _
        ByVal samDesired As Long, _
        ByRef phkResult As Long) As Long

    Private Declare Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" ( _
        ByVal hKey As Long, _
        ByVal lpSubKey As String, _
        ByVal Reserved As Long, _
        ByVal lpClass As String, _
        ByVal dwOptions As Long, _
        ByVal samDesired As Long, _
        ByVal lpSecurityAttributes As Long, _
        ByRef phkResult As Long, _
        ByRef lpdwDisposition As Long) As Long

    Private Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" ( _
        ByVal hKey As Long, _
        ByVal lpValueName As String, _
        ByVal Reserved As Long, _
        ByVal dwType As Long, _
        ByVal lpData As Long, _
        ByVal cbData As Long) As Long

    Private Declare Function RegQueryValueExString Lib "advapi32.dll" Alias "RegQueryValueExA" ( _
        ByVal hKey As Long, _
        ByVal lpValueName As String, _
        ByVal lpReserved As Long, _
        ByRef lpType As Long, _
        ByVal lpData As String, _
        ByRef lpcbData As Long) As Long

    Private Declare Function RegQueryValueExLong Lib "advapi32.dll" Alias "RegQueryValueExA" ( _
        ByVal hKey As Long, _
        ByVal lpValueName As String, _
        ByVal lpReserved As Long, _
        ByRef lpType As Long, _
        ByRef lpData As Long, _
        ByRef lpcbData As Long) As Long

    Private Declare Function RegCloseKey Lib "advapi32.dll" ( _
        ByVal hKey As Long) As Long

    Private Declare Function RegDeleteValue Lib "advapi32.dll" Alias "RegDeleteValueA" ( _
        ByVal hKey As Long, _
        ByVal lpValueName As String) As Long

    Private Declare Function RegDeleteKey Lib "advapi32.dll" Alias "RegDeleteKeyA" ( _
        ByVal hKey As Long, _
        ByVal lpSubKey As String) As Long
#End If

' HKEY 定数
Public Const HKEY_CURRENT_USER As Long = &H80000001

' アクセス権限
Public Const KEY_READ As Long = &H20019
Public Const KEY_WRITE As Long = &H20006
Public Const KEY_ALL_ACCESS As Long = &HF003F ' KEY_CREATE_KEY, KEY_CREATE_SUB_KEY, KEY_ENUM_SUB_KEYS, KEY_NOTIFY, KEY_QUERY_VALUE, KEY_SET_VALUE, KEY_READ, KEY_WRITE

' レジストリ値の型
Public Const REG_SZ As Long = 1             ' 文字列
Public Const REG_DWORD As Long = 4          ' 32ビット数値
Public Const REG_EXPAND_SZ As Long = 2      ' 展開可能な文字列
Public Const REG_BINARY As Long = 3         ' バイナリデータ

' レジストリ操作の戻り値 (成功)
Public Const ERROR_SUCCESS As Long = 0

' その他の定数
Public Const CREATE_NEW_KEY As Long = 1     ' キーが新規作成された
Public Const OPEN_EXISTING_KEY As Long = 2  ' 既存キーが開かれた
  • 前提: Office 2010以降(VBA7)を推奨。古いバージョンでも動作するよう#If VBA7 Thenディレクティブで分岐させています。

  • 入出力: 各API関数はWindowsのシステムコールを通じてレジストリを操作します。

  • 計算量: レジストリ操作はシステムコールであり、VBAレベルでの計算量分析は適切ではありません。OSのファイルI/Oに類似しており、操作あたりのオーバーヘッドは一定です。

2. レジストリキーの作成と値の設定 (文字列, DWORD)

' --- レジストリ操作ラッパー関数群 ---

' レジストリキーを開くか作成する関数
' 成功した場合はTrue、hKeyHandleにハンドルを格納
Public Function OpenOrCreateRegistryKey(ByVal strKeyPath As String, ByRef hKeyHandle As LongPtr) As Boolean
    Dim lResult As Long
    Dim dwDisposition As LongPtr

    ' まず開いてみる
    lResult = RegOpenKeyEx(HKEY_CURRENT_USER, strKeyPath, 0, KEY_ALL_ACCESS, hKeyHandle)
    If lResult = ERROR_SUCCESS Then
        OpenOrCreateRegistryKey = True
        Debug.Print "キーが開かれました: " & strKeyPath & " (ハンドル: " & hKeyHandle & ")"
        Exit Function
    End If

    ' 開けなければ作成
    lResult = RegCreateKeyEx(HKEY_CURRENT_USER, strKeyPath, 0, vbNullString, 0, KEY_ALL_ACCESS, 0, hKeyHandle, dwDisposition)
    If lResult = ERROR_SUCCESS Then
        OpenOrCreateRegistryKey = True
        If dwDisposition = CREATE_NEW_KEY Then
            Debug.Print "キーが新規作成されました: " & strKeyPath & " (ハンドル: " & hKeyHandle & ")"
        Else
            Debug.Print "既存キーが開かれました (RegCreateKeyEx経由): " & strKeyPath & " (ハンドル: " & hKeyHandle & ")"
        End If
    Else
        Debug.Print "キーの作成または開くのに失敗しました: " & strKeyPath & " (エラーコード: " & lResult & ")"
        OpenOrCreateRegistryKey = False
    End If
End Function

' 文字列値を設定する関数
Public Function SetRegistryStringValue(ByVal hKey As LongPtr, ByVal strValueName As String, ByVal strValue As String) As Boolean
    Dim lResult As Long
    Dim cbData As Long

    ' Null終端文字を含めるためLenB(strValue) + 2
    cbData = LenB(StrConv(strValue, vbFromUnicode)) + 1 ' ANSI文字列のバイト長 + Null終端文字

    ' RegSetValueExのlpData引数は、文字列の場合は先頭アドレスを渡す必要がある。
    ' VBAではByVal lpData As LongPtr と宣言し、StrPtr(strValue) を渡す。
    ' しかし、StrPtr(strValue)はUnicode文字列のポインタを返すため、ANSI API (RegSetValueExA) に直接渡すと文字化けする。
    ' そのため、ここではStrConvでANSIバイト列に変換した上で、VBAの内部的な文字列ポインタを渡す方法を用いる。
    ' より堅牢な方法としては、Byte配列に変換して渡し、VarPtr(bytearray(0)) をlpDataに指定する。
    ' 今回は簡略化のため、LongPtrを介して文字列ポインタを渡す形を採用。
    ' RegSetValueExA はANSI文字列を期待するため、VBAのUnicode文字列を直接渡すと問題が生じる。
    ' 厳密には、一旦バイト配列に変換して渡すか、RegSetValueExW (Unicode版) を利用する。
    ' ここでは lpData As LongPtr と宣言しているため、ANSI版では直接文字列を渡せない。
    ' よって、VBAの文字列をByValで渡すと、VBAが一時的なコピーを作成してポインタを渡してくれることを期待する。
    ' しかし、これが常に安全とは限らないため、以下の書き方を推奨。
    Dim bytData() As Byte
    bytData = StrConv(strValue, vbFromUnicode)
    cbData = UBound(bytData) - LBound(bytData) + 1 + 1 ' バイト配列長 + Null終端文字

    lResult = RegSetValueEx(hKey, strValueName, 0, REG_SZ, VarPtr(bytData(LBound(bytData))), cbData)
    If lResult = ERROR_SUCCESS Then
        SetRegistryStringValue = True
        Debug.Print "文字列値 '" & strValueName & "' が設定されました: " & strValue
    Else
        SetRegistryStringValue = False
        Debug.Print "文字列値 '" & strValueName & "' の設定に失敗しました (エラーコード: " & lResult & ")"
    End If
End Function

' DWORD (Long) 値を設定する関数
Public Function SetRegistryDWordValue(ByVal hKey As LongPtr, ByVal strValueName As String, ByVal lValue As Long) As Boolean
    Dim lResult As Long
    lResult = RegSetValueEx(hKey, strValueName, 0, REG_DWORD, VarPtr(lValue), 4) ' DWORDは4バイト
    If lResult = ERROR_SUCCESS Then
        SetRegistryDWordValue = True
        Debug.Print "DWORD値 '" & strValueName & "' が設定されました: " & lValue
    Else
        SetRegistryDWordValue = False
        Debug.Print "DWORD値 '" & strValueName & "' の設定に失敗しました (エラーコード: " & lResult & ")"
    End If
End Function

' --- テストプロシージャ (Excel/Accessから実行) ---
Public Sub Test_SetRegistryValues()
    Const TEST_KEY_PATH As String = "Software\MyVBAApp\Settings"
    Dim hKey As LongPtr
    Dim bSuccess As Boolean

    On Error GoTo ErrorHandler

    Debug.Print "--- レジストリ値設定テスト開始 ---"

    ' 1. キーのオープンまたは作成
    bSuccess = OpenOrCreateRegistryKey(TEST_KEY_PATH, hKey)
    If Not bSuccess Then GoTo CleanUp

    ' 2. 文字列値の設定
    bSuccess = SetRegistryStringValue(hKey, "UserName", "VBAUser")
    If Not bSuccess Then GoTo CleanUp

    bSuccess = SetRegistryStringValue(hKey, "LastAccessDate", Format(Date, "YYYY/MM/DD"))
    If Not bSuccess Then GoTo CleanUp

    ' 3. DWORD値の設定
    bSuccess = SetRegistryDWordValue(hKey, "RunCount", 123)
    If Not bSuccess Then GoTo CleanUp

    Debug.Print "--- レジストリ値設定テスト完了 ---"

CleanUp:
    If hKey <> 0 Then
        RegCloseKey hKey
        Debug.Print "レジストリハンドルを閉じました。"
    End If
    Exit Sub

ErrorHandler:
    Debug.Print "エラー発生: " & Err.Description
    Resume CleanUp
End Sub
  • RegSetValueExで文字列を扱う際は、APIがANSI版(RegSetValueExA)であるため、VBAのUnicode文字列をANSIバイト配列に変換する必要があります。VarPtr(bytData(LBound(bytData)))とすることで、バイト配列の先頭アドレスをAPIに渡します[2]。

  • VarPtr()関数は、変数のメモリ上のアドレスを返します。これにより、VBAの変数をAPIが直接参照できるようになります。

  • LongPtrは64bit環境で8バイト、32bit環境で4バイトとなり、ポインタやハンドルを格納するのに適しています。

3. レジストリ値の読み出し

' 文字列値を読み出す関数
Public Function GetRegistryStringValue(ByVal strKeyPath As String, ByVal strValueName As String, ByRef strResult As String) As Boolean
    Dim hKey As LongPtr
    Dim lResult As Long
    Dim lpType As Long
    Dim lpcbData As Long
    Dim sBuf As String
    Dim bSuccess As Boolean

    On Error GoTo ErrorHandler

    bSuccess = OpenOrCreateRegistryKey(strKeyPath, hKey)
    If Not bSuccess Then GoTo CleanUp

    ' 必要なバッファサイズを取得
    lResult = RegQueryValueExString(hKey, strValueName, 0, lpType, vbNullString, lpcbData)
    If lResult <> ERROR_SUCCESS Then
        Debug.Print "文字列値 '" & strValueName & "' の取得に失敗 (サイズ取得時)。エラーコード: " & lResult
        GoTo CleanUp
    End If

    ' バッファを適切なサイズに初期化
    ' RegQueryValueExAはANSIバイト長を返すため、UnicodeのVBA文字列に変換するときの文字数に注意。
    ' 簡単のため、ここではバッファサイズを最大255バイトとして固定的に確保
    ' より正確には、lpcbDataからUnicode文字数を計算し、SBuf = Space(char_count)とする
    sBuf = Space(lpcbData) ' ANSIバイト長分のスペースを確保 (実際にはUnicode文字長に相当)

    ' 値を読み出し
    lResult = RegQueryValueExString(hKey, strValueName, 0, lpType, sBuf, lpcbData)
    If lResult = ERROR_SUCCESS Then
        If lpType = REG_SZ Or lpType = REG_EXPAND_SZ Then
            ' Null終端文字を除去して取得
            strResult = Left$(sBuf, InStr(1, sBuf, vbNullChar) - 1)
            GetRegistryStringValue = True
            Debug.Print "文字列値 '" & strValueName & "' を読み出しました: " & strResult
        Else
            Debug.Print "文字列値 '" & strValueName & "' はREG_SZ型ではありません。検出型: " & lpType
            GetRegistryStringValue = False
        End If
    Else
        Debug.Print "文字列値 '" & strValueName & "' の読み出しに失敗しました (エラーコード: " & lResult & ")"
        GetRegistryStringValue = False
    End If

CleanUp:
    If hKey <> 0 Then
        RegCloseKey hKey
    End If
    Exit Function

ErrorHandler:
    Debug.Print "エラー発生: " & Err.Description
    Resume CleanUp
End Function

' DWORD (Long) 値を読み出す関数
Public Function GetRegistryDWordValue(ByVal strKeyPath As String, ByVal strValueName As String, ByRef lResultValue As Long) As Boolean
    Dim hKey As LongPtr
    Dim lResult As Long
    Dim lpType As Long
    Dim lpcbData As Long
    Dim bSuccess As Boolean

    On Error GoTo ErrorHandler

    bSuccess = OpenOrCreateRegistryKey(strKeyPath, hKey)
    If Not bSuccess Then GoTo CleanUp

    lpcbData = 4 ' DWORDは4バイト

    lResult = RegQueryValueExLong(hKey, strValueName, 0, lpType, lResultValue, lpcbData)
    If lResult = ERROR_SUCCESS Then
        If lpType = REG_DWORD Then
            GetRegistryDWordValue = True
            Debug.Print "DWORD値 '" & strValueName & "' を読み出しました: " & lResultValue
        Else
            Debug.Print "DWORD値 '" & strValueName & "' はREG_DWORD型ではありません。検出型: " & lpType
            GetRegistryDWordValue = False
        End If
    Else
        Debug.Print "DWORD値 '" & strValueName & "' の読み出しに失敗しました (エラーコード: " & lResult & ")"
        GetRegistryDWordValue = False
    End If

CleanUp:
    If hKey <> 0 Then
        RegCloseKey hKey
    End If
    Exit Function

ErrorHandler:
    Debug.Print "エラー発生: " & Err.Description
    Resume CleanUp
End Function

' --- テストプロシージャ (Excel/Accessから実行) ---
Public Sub Test_GetRegistryValues()
    Const TEST_KEY_PATH As String = "Software\MyVBAApp\Settings"
    Dim strUserName As String
    Dim strLastAccessDate As String
    Dim lRunCount As Long
    Dim bSuccess As Boolean

    Debug.Print "--- レジストリ値読み出しテスト開始 ---"

    ' 文字列値の読み出し
    bSuccess = GetRegistryStringValue(TEST_KEY_PATH, "UserName", strUserName)
    If bSuccess Then Debug.Print "読み出したUserName: " & strUserName

    bSuccess = GetRegistryStringValue(TEST_KEY_PATH, "LastAccessDate", strLastAccessDate)
    If bSuccess Then Debug.Print "読み出したLastAccessDate: " & strLastAccessDate

    ' DWORD値の読み出し
    bSuccess = GetRegistryDWordValue(TEST_KEY_PATH, "RunCount", lRunCount)
    If bSuccess Then Debug.Print "読み出したRunCount: " & lRunCount

    Debug.Print "--- レジストリ値読み出しテスト完了 ---"
End Sub
  • RegQueryValueExAで文字列を読み出す際、最初にlpcbDatavbNullStringを渡して必要なバッファサイズを取得し、次にそのサイズで文字列変数をSpace()で初期化してから再度APIを呼び出します[3]。

  • VBAのStringはUnicodeですが、RegQueryValueExAはANSIを扱うため、Space()で確保したバッファにANSIデータが書き込まれた後、VBAがそれをUnicodeとして解釈し、Null終端文字で文字列を切り詰める必要があります。

4. レジストリ値/キーの削除

' レジストリ値を削除する関数
Public Function DeleteRegistryValue(ByVal strKeyPath As String, ByVal strValueName As String) As Boolean
    Dim hKey As LongPtr
    Dim lResult As Long
    Dim bSuccess As Boolean

    On Error GoTo ErrorHandler

    bSuccess = OpenOrCreateRegistryKey(strKeyPath, hKey)
    If Not bSuccess Then GoTo CleanUp

    lResult = RegDeleteValue(hKey, strValueName)
    If lResult = ERROR_SUCCESS Then
        DeleteRegistryValue = True
        Debug.Print "レジストリ値 '" & strValueName & "' を削除しました。"
    Else
        DeleteRegistryValue = False
        Debug.Print "レジストリ値 '" & strValueName & "' の削除に失敗しました (エラーコード: " & lResult & ")"
    End If

CleanUp:
    If hKey <> 0 Then
        RegCloseKey hKey
    End If
    Exit Function

ErrorHandler:
    Debug.Print "エラー発生: " & Err.Description
    Resume CleanUp
End Function

' レジストリキーを削除する関数 (HKEY_CURRENT_USER配下のみ対応)
' サブキーが存在する場合は削除できません。サブキーも削除する場合は再帰処理が必要ですが、本記事では割愛。
Public Function DeleteRegistryKey(ByVal strKeyPath As String) As Boolean
    Dim lResult As Long

    On Error GoTo ErrorHandler

    lResult = RegDeleteKey(HKEY_CURRENT_USER, strKeyPath)
    If lResult = ERROR_SUCCESS Then
        DeleteRegistryKey = True
        Debug.Print "レジストリキー '" & strKeyPath & "' を削除しました。"
    Else
        DeleteRegistryKey = False
        Debug.Print "レジストリキー '" & strKeyPath & "' の削除に失敗しました (エラーコード: " & lResult & ")"
    End If

CleanUp:
    Exit Function

ErrorHandler:
    Debug.Print "エラー発生: " & Err.Description
    Resume CleanUp
End Function

' --- テストプロシージャ (Excel/Accessから実行) ---
Public Sub Test_DeleteRegistryValuesAndKey()
    Const TEST_KEY_PATH As String = "Software\MyVBAApp\Settings"
    Dim bSuccess As Boolean

    Debug.Print "--- レジストリ削除テスト開始 ---"

    ' 値の削除
    bSuccess = DeleteRegistryValue(TEST_KEY_PATH, "UserName")
    bSuccess = DeleteRegistryValue(TEST_KEY_PATH, "LastAccessDate")
    bSuccess = DeleteRegistryValue(TEST_KEY_PATH, "RunCount")

    ' キーの削除
    ' RegDeleteKeyはサブキーが存在すると失敗するため、Test_SetRegistryValuesを先に実行して
    ' 値のみを設定した状態でこのテストを実行してください。
    bSuccess = DeleteRegistryKey(TEST_KEY_PATH)

    Debug.Print "--- レジストリ削除テスト完了 ---"
End Sub
  • RegDeleteKeyは、削除対象のキーにサブキーが存在すると失敗します。サブキーを含めて削除するには再帰的な処理が必要です。今回は単純な値の管理を想定しているため、直接キーを削除する機能のみを提供します[4]。

検証

実行手順

  1. VBAエディタの起動: ExcelまたはAccessを開き、Alt + F11キーを押してVBAエディタを起動します。

  2. 標準モジュールの挿入: 左側のプロジェクトエクスプローラーで対象のプロジェクトを右クリックし、「挿入」→「標準モジュール」を選択します。

  3. コードの貼り付け: 上記「Win32 APIの宣言」のコードをモジュールに貼り付けます。続けて、「レジストリキーの作成と値の設定」「レジストリ値の読み出し」「レジストリ値/キーの削除」の各コードブロックも同じモジュールに貼り付けます。

  4. テストプロシージャの実行:

    • Test_SetRegistryValuesを実行し、レジストリキーと値が作成・設定されることを確認します。VBAエディタの「実行」メニューから「Sub/ユーザーフォームの実行」を選択するか、コード内でカーソルを置いてF5キーを押します。

    • Test_GetRegistryValuesを実行し、設定した値が正しく読み出されることを確認します。VBAエディタのイミディエイトウィンドウ(Ctrl + G)に結果が出力されます。

    • Test_DeleteRegistryValuesAndKeyを実行し、設定した値とキーが削除されることを確認します。

レジストリエディタでの確認

  1. Windows + Rキーを押し、「ファイル名を指定して実行」ダイアログにregeditと入力してEnterキーを押します。

  2. レジストリエディタで以下のパスに移動します。 コンピューター\HKEY_CURRENT_USER\Software\MyVBAApp\Settings

  3. Test_SetRegistryValues実行後、UserName (種類: REG_SZ), LastAccessDate (種類: REG_SZ), RunCount (種類: REG_DWORD) の各値が正しく作成されていることを確認します。

  4. Test_DeleteRegistryValuesAndKey実行後、上記パス下の値、最終的にはMyVBAApp\Settingsキー自体が削除されていることを確認します。

ロールバック方法

  • 手動によるロールバック:

    1. レジストリ操作を実行する前に、レジストリエディタで対象のキー (HKEY_CURRENT_USER\Software\MyVBAAppなど) を右クリックし、「エクスポート」を選択して.regファイルとして保存します。

    2. 問題が発生した場合は、保存した.regファイルをダブルクリックしてレジストリにインポートし、元の状態に戻します。

  • コードによるロールバック: 上記のTest_DeleteRegistryValuesAndKeyプロシージャは、作成したキーと値を削除する機能を提供します。テスト終了後にこのプロシージャを実行することで、レジストリをクリーンな状態に戻すことができます。

運用

セキュリティ上の注意点

  • 権限: HKEY_CURRENT_USERは現在のユーザープロファイルに紐付くため、通常は特別な管理者権限なしに操作できます。しかし、不適切なレジストリ操作はアプリケーションやOSの動作に悪影響を及ぼす可能性があります。

  • 信頼性: 未検証のコードや信頼できないソースからのコードでレジストリを操作することは避けるべきです。

  • 情報の保管: パスワードや機密性の高い情報をレジストリに平文で保存することは避けてください。暗号化するなどの対策が必要です。

複数ユーザー環境での考慮

HKEY_CURRENT_USERは各ユーザー固有の設定を格納するため、異なるユーザーが同じアプリケーションを使用した場合、それぞれ独立した設定がレジストリに保存されます。全ユーザー共通の設定を管理する場合は、HKEY_LOCAL_MACHINEを使用する必要がありますが、これには管理者権限が必要です。

落とし穴

  • データ型の不一致: RegSetValueExRegQueryValueExで指定するデータ型 (REG_SZ, REG_DWORDなど) がVBAの変数型と一致しない場合、値が正しく読み書きできない、またはクラッシュの原因となることがあります。特に文字列のANSI/Unicodeの違いには注意が必要です。

  • 64bit/32bit環境の違い: Declare PtrSafeLongPtrを使用しないと、64bit版Office環境でコンパイルエラーや実行時エラーが発生します。本コードは#If VBA7 Thenで対応済みです。

  • エラー処理の欠如: レジストリ操作は失敗する可能性があります (キーが存在しない、権限がないなど)。戻り値の確認と適切なエラーハンドリングが必須です。

  • キーの存在チェック: 存在しないキーに対して値を設定しようとするとエラーになる場合があります。RegCreateKeyExでキーを作成するか、RegOpenKeyExで開く前にキーの存在をチェックするなどの工夫が必要です。

性能チューニング

VBAによるレジストリ操作の性能は、主に以下の点に集約されます。

  • レジストリI/O回数の最小化: レジストリへのアクセスはファイルI/Oと同様に、VBAの内部処理よりもオーバーヘッドが大きいです。頻繁な読み書きは避け、必要な設定はアプリケーション起動時に一括で読み込み、終了時に一括で書き込むなど、I/O回数を最小限に抑える設計が重要です。

  • 必要なデータのみを操作: 不要なレジストリキーや値を作成・更新しないようにコードを設計します。

  • VBAの一般的なチューニングとの関連: ScreenUpdating = FalseApplication.Calculation = xlCalculationManualといったExcel/Access固有の性能チューニングは、UI更新や再計算を抑制するため、レジストリ操作自体には直接影響しません。しかし、レジストリから読み込んだ設定値に基づいて大規模な処理を実行する場合、これらの最適化はアプリケーション全体の応答性向上に寄与します。例えば、レジストリから取得した設定値でシート範囲を操作する際にScreenUpdatingを一時的に無効にすることで、視覚的なちらつきをなくし、処理速度を向上させることができます。

レジストリ操作の「性能」とは、VBAコードの実行速度というよりも、OSへのシステムコール回数と、それによって発生するI/O待ち時間の合計です。そのため、VBA側で配列バッファを導入してレジストリI/Oを減らすといった直接的な手法は適用しにくく、むしろアプリケーション設計の段階でレジストリアクセスのパターンを最適化することが肝要となります。

まとめ

本記事では、VBAからHKEY_CURRENT_USERレジストリを操作するためのWin32 API活用法を詳述しました。Declare PtrSafeLongPtrを駆使することで、64bit環境を含む現代のOffice環境でレジストリの読み書き、作成、削除が可能です。提供したコードはExcelおよびAccessで実用レベルの機能を提供し、レジストリエディタで動作確認もできます。

レジストリ操作は強力な機能ですが、その影響範囲も広いため、エラーハンドリングを徹底し、セキュリティと運用上の注意点を理解した上で慎重に利用することが重要です。このガイドが、VBAによるOffice自動化の可能性をさらに広げる一助となれば幸いです。


[1] LongPtr データ型. Microsoft Learn. 更新日: 2022年10月25日. URL: https://learn.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/longptr-data-type (参照日: {{jst_today}}) [2] RegSetValueExA function (winreg.h). Microsoft Learn. 更新日: 2022年7月4日. URL: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regsetvalueexa (参照日: {{jst_today}}) [3] RegQueryValueExA function (winreg.h). Microsoft Learn. 更新日: 2022年7月4日. URL: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryvalueexa (参照日: {{jst_today}})

[4] RegDeleteKeyA function (winreg.h). Microsoft Learn. 更新日: 2022年7月4日. URL: https://learn.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regdeletekeya (参照日: {{jst_today}})

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました