VBAからWin32 APIでレジストリを直接操作する実践ガイド

Tech

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

VBAからWin32 APIでレジストリを直接操作する実践ガイド

1. 背景と要件

Microsoft Officeアプリケーション(ExcelやAccessなど)を用いた業務自動化において、永続的な設定値の保存やアプリケーションの状態管理が必要となる場面は少なくありません。ファイルへの保存も可能ですが、Windows環境に固有の設定や、OS全体で共有されるべき情報、あるいはユーザーごとの設定を効率的かつセキュアに管理するためには、Windowsレジストリの利用が非常に有効です。

VBAにはレジストリを操作するための組み込み関数(SaveSetting, GetSetting, GetAllSettings, DeleteSetting)が存在しますが、これらは「ユーザーのアプリケーションデータ」という限定されたパス(通常 HKEY_CURRENT_USER\Software\VB and VBA Program Settings)のみを操作でき、かつ文字列型しか扱えないなど、機能に制約があります。 、VBAからWindows API(Win32 API)を直接呼び出し、レジストリをより詳細かつ柔軟に操作する方法を解説します。これにより、特定のレジストリパスへのアクセス、様々なデータ型(文字列、DWORDなど)の読み書き、キーの作成・削除といった高度なレジストリ操作が可能になります。外部ライブラリに依存せず、標準機能とWin32 APIのみで実装することを要件とします。

2. 設計

Win32 APIを利用したレジストリ操作は、以下の主要なAPI関数群を中心に構築されます。これらの関数は advapi32.dll に含まれており、VBAでは Declare PtrSafe ステートメントを用いて宣言します。PtrSafe キーワードは、32ビット版および64ビット版のOffice環境でVBAが正しくAPIを呼び出すために必須です。

2.1. Win32 APIの宣言と定数

VBAモジュールには、Win32 API関数と関連する定数を宣言するためのセクションが必要です。 主要なAPI関数は以下の通りです。

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

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

  • RegSetValueExA: 指定されたレジストリ値のデータを設定する。

  • RegGetValueA: 指定されたレジストリ値のデータを取得する。

  • RegCreateKeyExA: レジストリキーを作成するか、既存のキーを開く。

  • RegDeleteKeyExA: レジストリキーを削除する。

これらのAPIを使用するために必要な定数(例: HKEY_CURRENT_USERREG_SZ など)も定義します。

2.2. 共通モジュールの構成

これらのAPI宣言と、それらをラップするヘルパー関数をまとめて、modRegistry といった共通モジュールを作成します。これにより、コードの再利用性と保守性が向上します。

2.3. 処理フロー

レジストリ操作の基本的な処理フローは、以下のようになります。

graph TD
    A["処理開始"] --> B{"レジストリキーの存在確認"};
    B -- キーが存在しない場合 |RegCreateKeyExA| --> C["新しいレジストリキーを作成"];
    B -- キーがすでに存在する場合 |RegOpenKeyExA| --> D["既存のレジストリキーを開く"];
    C --> D_CONT["開かれたキーハンドルを取得"];
    D --> D_CONT;
    D_CONT --> E{"レジストリ値の操作"};
    E -- 値を設定する |RegSetValueExA| --> F["指定されたレジストリ値にデータを書き込む"];
    E -- 値を取得する |RegGetValueA| --> G["指定されたレジストリ値からデータを読み込む"];
    F --> H["操作完了"];
    G --> H;
    H --> I["RegCloseKey: レジストリキーを閉じる"];
    I --> J["処理終了"];

3. 実装

以下に、ExcelまたはAccess VBAで利用できるレジストリ操作のコード例を示します。VBE (Visual Basic Editor) を開き、新しい標準モジュールを挿入して以下のコードを貼り付けてください。

3.1. 共通API宣言とヘルパー関数モジュール

まず、modRegistry という名前の標準モジュールに以下のコードを記述します。

' // modRegistry.bas //
Option Explicit

' Win32 API関数の宣言 (PtrSafeは64ビット環境対応)
' advapi32.dll からレジストリ関連関数をインポート
#If VBA7 Then

    ' 64ビットOSの場合
    Private Declare PtrSafe Function RegOpenKeyExA Lib "advapi32.dll" (ByVal hKey As LongPtr, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As LongPtr) As Long
    Private Declare PtrSafe Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As LongPtr) As Long
    Private Declare PtrSafe Function RegSetValueExA Lib "advapi32.dll" (ByVal hKey As LongPtr, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpData As Any, ByVal cbData As Long) As Long
    Private Declare PtrSafe Function RegGetValueA Lib "advapi32.dll" (ByVal hkey As LongPtr, ByVal lpSubKey As String, ByVal lpValue As String, ByVal dwFlags As Long, pdwType As Long, pvData As Any, pcbData As Long) As Long
    Private Declare PtrSafe Function RegCreateKeyExA Lib "advapi32.dll" (ByVal hKey As LongPtr, ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, ByVal samDesired As Long, lpSecurityAttributes As Any, phkResult As LongPtr, lpdwDisposition As Long) As Long
    Private Declare PtrSafe Function RegDeleteKeyExA Lib "advapi32.dll" (ByVal hKey As LongPtr, ByVal lpSubKey As String, ByVal samDesired As Long, ByVal Reserved As Long) As Long
#Else

    ' 32ビットOSの場合
    Private Declare Function RegOpenKeyExA Lib "advapi32.dll" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, phkResult As Long) As Long
    Private Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
    Private Declare Function RegSetValueExA Lib "advapi32.dll" (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpData As Any, ByVal cbData As Long) As Long
    Private Declare Function RegGetValueA Lib "advapi32.dll" (ByVal hkey As Long, ByVal lpSubKey As String, ByVal lpValue As String, ByVal dwFlags As Long, pdwType As Long, pvData As Any, pcbData As Long) As Long
    Private Declare Function RegCreateKeyExA Lib "advapi32.dll" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, ByVal samDesired As Long, lpSecurityAttributes As Any, phkResult As Long, lpdwDisposition As Long) As Long
    Private Declare Function RegDeleteKeyExA Lib "advapi32.dll" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal samDesired As Long, ByVal Reserved As Long) As Long
#End If

' レジストリHKEY定数
Public Const HKEY_CURRENT_USER As LongPtr = &H80000001 ' 現在のユーザー設定
Public Const HKEY_LOCAL_MACHINE As LongPtr = &H80000002 ' ローカルコンピュータ設定

' アクセス権限定数
Public Const KEY_READ As Long = &H20019 ' 読み取りアクセス
Public Const KEY_WRITE As Long = &H20006 ' 書き込みアクセス (KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS)
Public Const KEY_ALL_ACCESS As Long = &H2003F ' 全てのアクセス権限

' レジストリ値の型定数
Public Const REG_SZ As Long = 1          ' 文字列 (Null終端文字列)
Public Const REG_DWORD As Long = 4       ' 32ビット数値

' RegGetValueAのフラグ定数
Public Const RRF_RT_ANY As Long = &HFFFF ' 任意の型

' API関数の戻り値 (成功を示す)
Public Const ERROR_SUCCESS As Long = 0

' ヘルパー関数: レジストリキーを作成または開く
' 戻り値: 成功ならTrue、失敗ならFalse
Public Function CreateOrOpenRegistryKey(ByVal hKeyParent As LongPtr, ByVal sSubKey As String, ByRef hKey As LongPtr) As Boolean
    Dim lRet As Long
    Dim lDisposition As Long

    ' キーを作成または開く
    lRet = RegCreateKeyExA(hKeyParent, sSubKey, 0, "", 0, KEY_ALL_ACCESS, ByVal 0&, hKey, lDisposition)

    If lRet = ERROR_SUCCESS Then
        CreateOrOpenRegistryKey = True
    Else
        Debug.Print "レジストリキーの作成/オープンに失敗しました。エラーコード: " & lRet
        CreateOrOpenRegistryKey = False
    End If
End Function

' ヘルパー関数: レジストリキーを閉じる
' 戻り値: 成功ならTrue、失敗ならFalse
Public Function CloseRegistryKey(ByRef hKey As LongPtr) As Boolean
    Dim lRet As Long
    If hKey <> 0 Then
        lRet = RegCloseKey(hKey)
        If lRet = ERROR_SUCCESS Then
            hKey = 0 ' ハンドルをリセット
            CloseRegistryKey = True
        Else
            Debug.Print "レジストリキーのクローズに失敗しました。エラーコード: " & lRet
            CloseRegistryKey = False
        End If
    Else
        CloseRegistryKey = True ' 元々開かれていない場合は成功とみなす
    End If
End Function

' ヘルパー関数: 文字列値をレジストリに書き込む
' sRootKey: HKEY_CURRENT_USER など
' sSubKey: サブキーのパス
' sValueName: 値の名前
' sValue: 書き込む文字列
Public Function SetRegistryString(ByVal sRootKey As LongPtr, ByVal sSubKey As String, ByVal sValueName As String, ByVal sValue As String) As Boolean
    Dim hKey As LongPtr
    Dim lRet As Long
    Dim bResult As Boolean

    bResult = CreateOrOpenRegistryKey(sRootKey, sSubKey, hKey)
    If Not bResult Then Exit Function ' キーのオープン/作成に失敗

    ' ANSI文字列に変換して書き込む (RegSetValueExAはANSIを期待)
    Dim bData() As Byte
    bData = StrConv(sValue, vbFromUnicode)

    ' Null終端を追加
    ReDim Preserve bData(LBound(bData) To UBound(bData) + 1)
    bData(UBound(bData)) = 0

    lRet = RegSetValueExA(hKey, sValueName, 0, REG_SZ, ByVal bData(0), UBound(bData) + 1) ' サイズはバイト数

    If lRet = ERROR_SUCCESS Then
        SetRegistryString = True
    Else
        Debug.Print "レジストリ値の書き込みに失敗しました。エラーコード: " & lRet
        SetRegistryString = False
    End If

    Call CloseRegistryKey(hKey)
End Function

' ヘルパー関数: DWORD (数値) 値をレジストリに書き込む
' sRootKey: HKEY_CURRENT_USER など
' sSubKey: サブキーのパス
' sValueName: 値の名前
' lValue: 書き込む数値
Public Function SetRegistryDWORD(ByVal sRootKey As LongPtr, ByVal sSubKey As String, ByVal sValueName As String, ByVal lValue As Long) As Boolean
    Dim hKey As LongPtr
    Dim lRet As Long
    Dim bResult As Boolean

    bResult = CreateOrOpenRegistryKey(sRootKey, sSubKey, hKey)
    If Not bResult Then Exit Function

    lRet = RegSetValueExA(hKey, sValueName, 0, REG_DWORD, lValue, 4) ' DWORDは4バイト

    If lRet = ERROR_SUCCESS Then
        SetRegistryDWORD = True
    Else
        Debug.Print "レジストリ値の書き込みに失敗しました。エラーコード: " & lRet
        SetRegistryDWORD = False
    End If

    Call CloseRegistryKey(hKey)
End Function

' ヘルパー関数: 文字列値をレジストリから読み込む
' sRootKey: HKEY_CURRENT_USER など
' sSubKey: サブキーのパス
' sValueName: 値の名前
' 戻り値: 読み込んだ文字列。失敗した場合は空文字列。
Public Function GetRegistryString(ByVal sRootKey As LongPtr, ByVal sSubKey As String, ByVal sValueName As String) As String
    Dim hKey As LongPtr
    Dim lRet As Long
    Dim lType As Long
    Dim lDataLen As Long
    Dim bData() As Byte
    Dim sResult As String
    Dim bResult As Boolean

    ' キーを開く (読み取り権限)
    lRet = RegOpenKeyExA(sRootKey, sSubKey, 0, KEY_READ, hKey)
    If lRet <> ERROR_SUCCESS Then
        Debug.Print "レジストリキーのオープンに失敗しました。エラーコード: " & lRet
        GetRegistryString = ""
        Exit Function
    End If

    ' まずデータサイズを取得
    lRet = RegGetValueA(hKey, "", sValueName, RRF_RT_ANY, lType, ByVal 0&, lDataLen)
    If lRet <> ERROR_SUCCESS Then
        Debug.Print "レジストリ値のサイズ取得に失敗しました。エラーコード: " & lRet
        Call CloseRegistryKey(hKey)
        GetRegistryString = ""
        Exit Function
    End If

    ' データ型がREG_SZであることを確認 (オプション)
    If lType <> REG_SZ Then
        Debug.Print "レジストリ値の型がREG_SZではありません。"
        Call CloseRegistryKey(hKey)
        GetRegistryString = ""
        Exit Function
    End If

    ' バッファを確保
    ReDim bData(0 To lDataLen - 1)

    ' 値を読み込む
    lRet = RegGetValueA(hKey, "", sValueName, RRF_RT_ANY, lType, ByVal bData(0), lDataLen)
    If lRet = ERROR_SUCCESS Then
        ' Null終端を削除し、Unicodeに変換
        If lDataLen > 0 And bData(lDataLen - 1) = 0 Then lDataLen = lDataLen - 1 ' 最後のNullバイトを削除
        sResult = StrConv(bData, vbUnicode)
        GetRegistryString = Left$(sResult, (lDataLen \ 2) - 1) ' VBAの文字列長に合わせる
        ' RegGetValueAはバイト数を返すが、VBAの文字列はUnicode(2バイト文字)なので、
        ' 実際にはlDataLen / 2 が文字数に近いが、Null終端もあるので注意が必要。
        ' StrConv(bData, vbUnicode) はNull終端まで変換してしまうため、
        ' 正確な文字列長を確保するために Left$ を使う。
    Else
        Debug.Print "レジストリ値の読み込みに失敗しました。エラーコード: " & lRet
        GetRegistryString = ""
    End If

    Call CloseRegistryKey(hKey)
End Function

' ヘルパー関数: DWORD (数値) 値をレジストリから読み込む
' sRootKey: HKEY_CURRENT_USER など
' sSubKey: サブキーのパス
' sValueName: 値の名前
' 戻り値: 読み込んだ数値。失敗した場合は0。
Public Function GetRegistryDWORD(ByVal sRootKey As LongPtr, ByVal sSubKey As String, ByVal sValueName As String) As Long
    Dim hKey As LongPtr
    Dim lRet As Long
    Dim lType As Long
    Dim lValue As Long
    Dim lDataLen As Long
    Dim bResult As Boolean

    ' キーを開く (読み取り権限)
    lRet = RegOpenKeyExA(sRootKey, sSubKey, 0, KEY_READ, hKey)
    If lRet <> ERROR_SUCCESS Then
        Debug.Print "レジストリキーのオープンに失敗しました。エラーコード: " & lRet
        GetRegistryDWORD = 0
        Exit Function
    End If

    lDataLen = 4 ' DWORDは4バイト
    lRet = RegGetValueA(hKey, "", sValueName, RRF_RT_ANY, lType, lValue, lDataLen) ' ByVal lValue

    If lRet = ERROR_SUCCESS Then
        If lType = REG_DWORD Then
            GetRegistryDWORD = lValue
        Else
            Debug.Print "レジストリ値の型がREG_DWORDではありません。"
            GetRegistryDWORD = 0
        End If
    Else
        Debug.Print "レジストリ値の読み込みに失敗しました。エラーコード: " & lRet
        GetRegistryDWORD = 0
    End If

    Call CloseRegistryKey(hKey)
End Function

' ヘルパー関数: レジストリキーを削除する
' sRootKey: HKEY_CURRENT_USER など
' sSubKey: 削除するサブキーのパス
' 戻り値: 成功ならTrue、失敗ならFalse
Public Function DeleteRegistryKey(ByVal sRootKey As LongPtr, ByVal sSubKey As String) As Boolean
    Dim lRet As Long

    ' RegDeleteKeyExA は直接サブキーを削除できる
    ' KEY_ALL_ACCESS は親キーのアクセス権限に影響する場合があるため、KEY_WRITE など適切な権限を考慮
    ' ここでは簡略化のため KEY_ALL_ACCESS
    lRet = RegDeleteKeyExA(sRootKey, sSubKey, KEY_ALL_ACCESS, 0)

    If lRet = ERROR_SUCCESS Then
        DeleteRegistryKey = True
    Else
        Debug.Print "レジストリキーの削除に失敗しました。エラーコード: " & lRet
        DeleteRegistryKey = False
    End If
End Function

コードに関する補足:

  • #If VBA7 Then ... #Else ... #End If は、64ビット版Officeと32ビット版Officeの互換性を確保するための記述です。PtrSafeキーワードとLongPtr型がVBA7 (Office 2010以降) で導入されたためです。

  • RegSetValueExA はANSI文字列を期待するため、VBAのUnicode文字列を StrConv(sValue, vbFromUnicode) でANSIバイト配列に変換しています。また、レジストリの文字列はNull終端が必須なので、バイト配列の末尾に 0 (Nullバイト) を追加しています。

  • RegGetValueA で文字列を読み出す際も、返されたバイト配列を StrConv(bData, vbUnicode) でVBAのUnicode文字列に戻しています。Null終端の処理に注意が必要です。

  • ByVal 0&NULL ポインタに相当し、API呼び出し時に使われます。

  • KEY_ALL_ACCESS は最大限のアクセス権限を与えますが、セキュリティの観点からは必要最小限の権限(KEY_READKEY_SET_VALUEなど)を指定することが推奨されます。

3.2. 実装例1: Excelでのアプリケーション設定管理

この例では、Excelアプリケーションの特定のユーザー設定(例: 設定ファイルのパス、最終更新日時)を HKEY_CURRENT_USER に保存・読み込みます。

' // 標準モジュール (例: modExcelSettings) またはシートモジュール //
Option Explicit

Private Const MY_APP_REG_PATH As String = "Software\MyCompany\ExcelApp\Settings" ' 任意のレジストリパス

' アプリケーション設定をレジストリに保存する
Public Sub SaveApplicationSettings()
    Dim sFilePath As String
    Dim lUpdateCount As Long
    Dim sLastUser As String

    On Error GoTo ErrorHandler

    ' ユーザーから設定値を取得する想定
    sFilePath = Application.GetOpenFilename("Excel Files (*.xlsx),*.xlsx", , "設定ファイルを選択")
    If sFilePath = "False" Then
        MsgBox "ファイル選択がキャンセルされました。", vbInformation
        Exit Sub
    End If
    lUpdateCount = GetRegistryDWORD(HKEY_CURRENT_USER, MY_APP_REG_PATH, "UpdateCount") + 1
    sLastUser = Environ("USERNAME") ' 現在のユーザー名を取得

    ' レジストリに書き込み
    If modRegistry.SetRegistryString(HKEY_CURRENT_USER, MY_APP_REG_PATH, "SettingFilePath", sFilePath) Then
        Debug.Print "SettingFilePath 保存成功: " & sFilePath
    Else
        Debug.Print "SettingFilePath 保存失敗"
    End If

    If modRegistry.SetRegistryDWORD(HKEY_CURRENT_USER, MY_APP_REG_PATH, "UpdateCount", lUpdateCount) Then
        Debug.Print "UpdateCount 保存成功: " & lUpdateCount
    Else
        Debug.Print "UpdateCount 保存失敗"
    End If

    If modRegistry.SetRegistryString(HKEY_CURRENT_USER, MY_APP_REG_PATH, "LastUpdatedUser", sLastUser) Then
        Debug.Print "LastUpdatedUser 保存成功: " & sLastUser
    Else
        Debug.Print "LastUpdatedUser 保存失敗"
    End If

    MsgBox "設定がレジストリに保存されました。", vbInformation

    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
End Sub

' アプリケーション設定をレジストリから読み込む
Public Sub LoadApplicationSettings()
    Dim sFilePath As String
    Dim lUpdateCount As Long
    Dim sLastUser As String

    On Error GoTo ErrorHandler

    ' レジストリから読み込み
    sFilePath = modRegistry.GetRegistryString(HKEY_CURRENT_USER, MY_APP_REG_PATH, "SettingFilePath")
    lUpdateCount = modRegistry.GetRegistryDWORD(HKEY_CURRENT_USER, MY_APP_REG_PATH, "UpdateCount")
    sLastUser = modRegistry.GetRegistryString(HKEY_CURRENT_USER, MY_APP_REG_PATH, "LastUpdatedUser")

    If sFilePath <> "" Then
        MsgBox "設定ファイルパス: " & sFilePath & vbCrLf & _
               "更新回数: " & lUpdateCount & vbCrLf & _
               "最終更新ユーザー: " & sLastUser, vbInformation
    Else
        MsgBox "設定が見つかりません。最初に設定を保存してください。", vbInformation
    End If

    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
End Sub

' アプリケーション設定をレジストリから削除する (ロールバック用)
Public Sub DeleteApplicationSettings()
    If MsgBox("本当にアプリケーション設定をレジストリから削除しますか?", vbYesNo + vbExclamation, "設定削除の確認") = vbYes Then
        If modRegistry.DeleteRegistryKey(HKEY_CURRENT_USER, MY_APP_REG_PATH) Then
            MsgBox "アプリケーション設定が正常に削除されました。", vbInformation
        Else
            MsgBox "アプリケーション設定の削除に失敗しました。", vbCritical
        End If
    End If
End Sub

実行手順 (Excel):

  1. Excelを開き、Alt + F11 でVBEを開きます。

  2. 「挿入」メニューから「標準モジュール」を選択し、modRegistry のコードを貼り付けます。

  3. もう一つ「標準モジュール」を挿入し、modExcelSettings と名前を付け(または既存のモジュールに)、上記Excelの実装例のコードを貼り付けます。

  4. SaveApplicationSettings または LoadApplicationSettings サブルーチンを実行します(VBEのツールバーの▶ボタン、またはExcelシートにボタンを配置してマクロを割り当て)。

  5. regedit.exe を起動し、HKEY_CURRENT_USER\Software\MyCompany\ExcelApp\Settings パスで保存されたレジストリ値を確認できます。

3.3. 実装例2: Accessでのユーザー別設定管理

この例では、Accessデータベースを使用するユーザーごとに、特定のフォームの表示設定やレポートの出力パスなどを HKEY_CURRENT_USER に保存・読み込みます。ユーザー名を含むサブキーを作成することで、複数ユーザー環境での設定衝突を防ぎます。

' // 標準モジュール (例: modAccessSettings) //
Option Explicit

' ユーザー固有のレジストリパスを作成
' CurrentUser は Environ("USERNAME") や Application.CurrentUser から取得
Private Function GetUserRegistryPath(ByVal sAppName As String, ByVal sSettingName As String) As String
    Dim sUserName As String
    sUserName = Environ("USERNAME") ' または Application.CurrentUser for Access security
    GetUserRegistryPath = "Software\MyCompany\" & sAppName & "\" & sUserName & "\" & sSettingName
End Function

' フォーム表示設定をレジストリに保存する
Public Sub SaveFormDisplaySetting(ByVal sFormName As String, ByVal bVisible As Boolean, ByVal lLeft As Long, ByVal lTop As Long)
    Dim sRegPath As String
    Dim lVisible As Long ' BOOLをDWORDとして保存

    On Error GoTo ErrorHandler

    sRegPath = GetUserRegistryPath("AccessApp", "Forms\" & sFormName)
    lVisible = IIf(bVisible, 1, 0)

    If modRegistry.SetRegistryDWORD(HKEY_CURRENT_USER, sRegPath, "Visible", lVisible) Then
        Debug.Print "Form '" & sFormName & "' Visible 保存成功: " & bVisible
    Else
        Debug.Print "Form '" & sFormName & "' Visible 保存失敗"
    End If

    If modRegistry.SetRegistryDWORD(HKEY_CURRENT_USER, sRegPath, "Left", lLeft) Then
        Debug.Print "Form '" & sFormName & "' Left 保存成功: " & lLeft
    Else
        Debug.Print "Form '" & sFormName & "' Left 保存失敗"
    End If

    If modRegistry.SetRegistryDWORD(HKEY_CURRENT_USER, sRegPath, "Top", lTop) Then
        Debug.Print "Form '" & sFormName & "' Top 保存成功: " & lTop
    Else
        Debug.Print "Form '" & sFormName & "' Top 保存失敗"
    End If

    MsgBox "'" & sFormName & "' の表示設定がレジストリに保存されました。", vbInformation

    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
End Sub

' フォーム表示設定をレジストリから読み込む
Public Sub LoadFormDisplaySetting(ByVal sFormName As String, ByRef bVisible As Boolean, ByRef lLeft As Long, ByRef lTop As Long)
    Dim sRegPath As String
    Dim lVisible As Long

    On Error GoTo ErrorHandler

    sRegPath = GetUserRegistryPath("AccessApp", "Forms\" & sFormName)

    lVisible = modRegistry.GetRegistryDWORD(HKEY_CURRENT_USER, sRegPath, "Visible")
    lLeft = modRegistry.GetRegistryDWORD(HKEY_CURRENT_USER, sRegPath, "Left")
    lTop = modRegistry.GetRegistryDWORD(HKEY_CURRENT_USER, sRegPath, "Top")

    bVisible = (lVisible = 1)

    If bVisible Or lLeft <> 0 Or lTop <> 0 Then ' 何らかの設定値があれば
        MsgBox "'" & sFormName & "' の表示設定を読み込みました:" & vbCrLf & _
               "表示: " & IIf(bVisible, "True", "False") & vbCrLf & _
               "左位置: " & lLeft & vbCrLf & _
               "上位置: " & lTop, vbInformation
    Else
        MsgBox "'" & sFormName & "' の設定が見つかりません。デフォルト値を使用します。", vbInformation
        bVisible = True ' デフォルト値
        lLeft = 100
        lTop = 100
    End If

    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
End Sub

' Accessユーザー設定をレジストリから削除する (ロールバック用)
Public Sub DeleteAccessUserSettings()
    Dim sRegPathBase As String
    Dim sUserName As String

    sUserName = Environ("USERNAME")
    sRegPathBase = "Software\MyCompany\AccessApp\" & sUserName

    If MsgBox("本当にAccessのユーザー設定をレジストリから削除しますか?" & vbCrLf & _
              "(パス: HKEY_CURRENT_USER\" & sRegPathBase & ")", vbYesNo + vbExclamation, "設定削除の確認") = vbYes Then
        If modRegistry.DeleteRegistryKey(HKEY_CURRENT_USER, sRegPathBase) Then
            MsgBox "Accessユーザー設定が正常に削除されました。", vbInformation
        Else
            MsgBox "Accessユーザー設定の削除に失敗しました。", vbCritical
        End If
    End If
End Sub

実行手順 (Access):

  1. Accessデータベースを開き、Alt + F11 でVBEを開きます。

  2. 「挿入」メニューから「標準モジュール」を選択し、modRegistry のコードを貼り付けます。

  3. もう一つ「標準モジュール」を挿入し、modAccessSettings と名前を付け(または既存のモジュールに)、上記Accessの実装例のコードを貼り付けます。

  4. イミディエイトウィンドウ (Ctrl + G) で Call SaveFormDisplaySetting("MyForm", True, 100, 50) のように実行します。

  5. regedit.exe を起動し、HKEY_CURRENT_USER\Software\MyCompany\AccessApp\<ユーザー名>\Forms\MyForm パスで保存されたレジストリ値を確認できます。

4. 検証

4.1. 各機能の動作確認

上記のコードを実行し、以下の項目を検証します。

  • 文字列の書き込みと読み込み: SetRegistryStringGetRegistryString を使って、日本語を含む様々な長さの文字列が正しく保存・復元されるか。

  • DWORDの書き込みと読み込み: SetRegistryDWORDGetRegistryDWORD を使って、正の数、負の数(VBAのLong型範囲内)、0が正しく保存・復元されるか。

  • キーの作成: 存在しないサブキーパスに対して SetRegistryStringSetRegistryDWORD を呼び出した際に、新しいキーが自動的に作成されるか。

  • キーの削除: DeleteRegistryKey を呼び出した際に、指定したキーとその配下の値が正しく削除されるか。

  • エラーハンドリング: 存在しないキーからの読み込み、権限のないレジストリパスへの書き込みなどを試行し、Debug.Print でエラーメッセージが表示され、VBAコードが異常終了しないか確認します。

  • 64ビット環境での動作: 64ビット版のOfficeで Declare PtrSafe が正しく機能し、レジストリ操作が行われるか確認します。

4.2. 性能評価

レジストリ操作自体は非常に高速であり、通常は数ミリ秒のオーダーで完了します。大量のデータを扱う場合でも、1000回程度の単純な読み書きであれば数十ミリ秒から数百ミリ秒程度で完了することが多いです。

ただし、頻繁なレジストリ操作、特にキーの開閉を繰り返すことはオーバーヘッドを生じさせます。例えば、1000個の値をループで書き込む際に、毎回キーを開閉すると、キー開閉のオーバーヘッドが積み重なり、単純に値を書き込むだけの処理に比べて数倍から数十倍の時間がかかる可能性があります。

処理内容 実行時間 (平均、目安)
単一値の読み書き 0.1ms – 1ms
1000個の値をバッチ処理で書き込み (キー開閉1回) 20ms – 50ms
1000個の値をループで書き込み (キー開閉1000回) 200ms – 500ms

この性能差を最小限に抑えるため、ヘルパー関数ではキーの開閉を各関数内で完結させていますが、もし一度に多数の値をまとめて操作する必要がある場合は、キーハンドルを呼び出し元で管理し、複数の RegSetValueExARegGetValueA を一度のキー開閉の中で実行するバッチ処理を実装することで、性能をさらに向上させることができます。

VBA全体の実行速度向上には、レジストリ操作に直接関連しない以下のチューニングも有効です。

  • Application.ScreenUpdating = False: 画面描画の更新を停止し、UI操作が多い場合に数秒〜数十秒の高速化が見込めます。

  • Application.Calculation = xlCalculationManual (Excelの場合): Excelの再計算を停止し、大量の数式を含むシートを操作する際に数秒〜数十秒の高速化が見込めます。

  • 配列バッファの利用: 大量のデータをシートから読み書きする際は、直接セルにアクセスせず、一度配列に読み込んで処理し、書き戻すことで、数百ミリ秒〜数秒の高速化が期待できます。

これらのチューニングは、レジストリ操作自体の高速化には寄与しませんが、VBAアプリケーション全体の体感速度を大幅に改善する上で重要です。

5. 運用

5.1. 配布と権限

  • 配布: VBAコードをExcelブック (.xlsm) やAccessデータベース (.accdb) に含めて配布します。特別なDLLやセットアップは不要です。

  • 権限: HKEY_CURRENT_USER 以下のレジストリキーは、通常、現在のユーザーが自由に読み書きできます。しかし、HKEY_LOCAL_MACHINE などのシステムレベルのキーを操作する場合は、管理者権限が必要になることがあります。アプリケーションが管理者権限なしで実行される場合、システムレベルのレジストリ操作は失敗します。

5.2. セキュリティ上の注意点

  • 機密情報の保存: レジストリはパスワードなどの機密情報を平文で保存する場所ではありません。セキュリティ要件に応じて、暗号化などの対策を講じる必要があります。

  • 不適切なパス: システムの安定性に関わるレジストリキー(例: HKEY_LOCAL_MACHINE\SYSTEM)を安易に操作すると、OSの動作に深刻な影響を与える可能性があります。自作アプリケーション用のパス (HKEY_CURRENT_USER\Software\MyCompany\... など) に限定して使用することを強く推奨します。

5.3. ロールバック方法

コード例には、DeleteApplicationSettings および DeleteAccessUserSettings サブルーチンを含んでいます。これらを実行することで、各アプリケーションがレジストリに書き込んだ設定キーを削除し、初期状態に戻すことができます。

実行手順:

  1. VBEを開き、対象のモジュール(例: modExcelSettings)を選択します。

  2. DeleteApplicationSettings サブルーチン(またはAccessの場合は DeleteAccessUserSettings)を実行します。

  3. 確認メッセージが表示されるので、「はい」を選択して削除を実行します。

6. 落とし穴と対策

6.1. 64bit/32bit互換性 (PtrSafe と LongPtr)

  • 落とし穴: Office 2010以降、64ビット版のOfficeが普及しました。32ビット版Office向けに書かれた古いVBAコードは、Declare ステートメントに PtrSafe キーワードがなく、ポインタやハンドルを Long 型で宣言しているため、64ビット環境で実行するとエラー(「DLL呼び出しエラー」など)が発生します。

  • 対策: Declare ステートメントには必ず PtrSafe キーワードを追加し、ポインタやハンドルを受け渡す引数や戻り値の型は LongPtr に変更します。また、#If VBA7 Then ... #Else ... #End If ディレクティブを使用して、Officeのバージョンに応じた宣言を使い分けます。本記事のコードはこの対策を講じています。

6.2. エラーハンドリング

  • 落とし穴: Win32 APIは成功時に ERROR_SUCCESS (0) を返しますが、失敗時には様々なエラーコードを返します。これを無視すると、意図しない動作やVBAのクラッシュにつながる可能性があります。

  • 対策: API関数の戻り値を必ずチェックし、ERROR_SUCCESS 以外の場合は適切なエラー処理(ログ出力、ユーザーへの通知、代替処理の実行など)を行います。VBAの On Error GoTo ステートメントと組み合わせて、堅牢なコードを記述します。

6.3. レジストリパスの罠

  • 落とし穴: レジストリパスは文字列で指定するため、タイプミスや不適切なパスの指定が容易に起こり得ます。存在しないパスへのアクセスはエラーとなり、システムパスへの誤った操作は深刻な問題を引き起こします。

  • 対策: アプリケーション固有の設定は、HKEY_CURRENT_USER\Software\貴社名\アプリケーション名 のように、明確な階層を持つ専用のパスを使用します。パスは定数として定義し、再利用性を高め、入力ミスを防ぎます。

7. まとめ

VBAからWin32 APIを直接操作することで、VBAの組み込み関数では実現できない高度なレジストリ操作が可能になります。Declare PtrSafe を用いたAPI宣言、適切な定数の定義、そしてヘルパー関数の作成により、レジストリ値の読み書き、キーの作成・削除といった機能を安全かつ効率的に実装できます。

本記事で示したコード例は、ExcelやAccessなどのOfficeアプリケーションにおける永続的な設定値の管理やアプリケーション状態の保存に活用できます。Win32 APIの知識は、Office自動化の可能性を大きく広げ、より高度でWindowsと密接に連携するソリューションを構築するための強力なツールとなるでしょう。ただし、レジストリ操作はシステムの根幹に関わるため、慎重な設計と堅牢なエラーハンドリングが不可欠です。

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

コメント

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