【ExcelVBA・マクロ】PtrSafeキーワードの使い方|32bit/64bit対応のAPI宣言とLongPtr型を完全解説【コピペOK】

ExcelVBA

ExcelVBAでWindows APIを使ったマクロを他のパソコンで実行したら、「コンパイルエラー:ステートメントが正しくありません」と表示されて動かなかった経験はありませんか。多くの場合、原因はOfficeのビット数(32bit版か64bit版か)の違いです。

この記事では、32bit環境と64bit環境の両方で正しく動作するAPI宣言の書き方を、PtrSafeキーワードとLongPtr型を中心に解説します。既存のAPI宣言コードを64bit対応に書き換える手順も紹介するので、エラーに困っている方はぜひ参考にしてください。

スポンサーリンク
スポンサーリンク

PtrSafeとは何か・なぜ必要なのか

PtrSafeは、Windows APIをVBAから呼び出すDeclareステートメントに付けるキーワードです。Office 2010以降で追加された64bit版VBA(通称VBA7)で、API宣言を使うために必須になりました。

64bit環境ではメモリアドレスの幅が32bitから64bitに広がったため、ウィンドウハンドルやポインタを扱うAPIをそれまでと同じLong型で受け取ると、値が入りきらずに不具合が起きる可能性があります。PtrSafeは「このAPI宣言は64bit環境でも安全に動作するように書かれています」とVBAに伝えるためのキーワードです。

64bit版ExcelでPtrSafeのないDeclareステートメントを実行すると、次のようなコンパイルエラーになります。

コンパイルエラー:
64 ビット システムでは、Declare ステートメントで PtrSafe 属性が必要です

32bit・64bit両対応にする条件付きコンパイル

32bit環境と64bit環境の両方で動くマクロを配布したい場合は、#Ifを使った条件付きコンパイルで宣言を分岐させます。VBA7(Office 2010以降)かどうかを判定するVBA7定数を使うのが基本形です。

#If VBA7 Then
    ' Office 2010以降(32bit / 64bit 共通)
    Private Declare PtrSafe Sub Sleep Lib "kernel32" ( _
        ByVal dwMilliseconds As Long)
#Else
    ' Office 2007以前(32bitのみ)
    Private Declare Sub Sleep Lib "kernel32" ( _
        ByVal dwMilliseconds As Long)
#End If

VBA7はOfficeが2010以降かどうかを表す定数で、真偽にかかわらずビルド時に判定されるため、片方のブロックしかコンパイルされません。そのため、Office 2007以前専用の構文が混在していてもエラーになりません。

VBA7とWin64の違い

条件分岐にはもう一つWin64という定数もあります。役割の違いは次の通りです。

  • VBA7:VBAのバージョンが7以降か(Office 2010以降か)を判定する
  • Win64:VBA自体が64bit版として動作しているかを判定する

ポインタサイズを厳密に切り替えたい場合は、両方を組み合わせて使います。

#If VBA7 And Win64 Then
    ' 64bit版Office
#ElseIf VBA7 Then
    ' 32bit版Office(Office 2010以降)
#Else
    ' Office 2007以前
#End If

LongPtr型の使い方

64bit環境ではメモリアドレスやウィンドウハンドルが64bitの値になるため、Long型(32bit)では収まらないケースがあります。これに対応するのがLongPtr型です。

LongPtrは、32bit環境では自動的に4バイトのLongとして、64bit環境では自動的に8バイトの整数として扱われる可変長の型です。API宣言の戻り値やハンドルを受け取る引数には、LongではなくLongPtrを使うのが安全です。

#If VBA7 Then
    Private Declare PtrSafe Function FindWindow Lib "user32" _
        Alias "FindWindowA" ( _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As LongPtr
#Else
    Private Declare Function FindWindow Lib "user32" _
        Alias "FindWindowA" ( _
        ByVal lpClassName As String, _
        ByVal lpWindowName As String) As Long
#End If

戻り値を受け取る変数側も、LongではなくLongPtrで宣言しておくと、32bit・64bitどちらの環境でも型の不一致エラーを避けられます。

Sub ShowWindowHandle()

    Dim hWnd As LongPtr
    hWnd = FindWindow(vbNullString, Application.Caption)

    If hWnd = 0 Then
        MsgBox "ウィンドウが見つかりませんでした"
    Else
        MsgBox "ウィンドウハンドル: " & hWnd
    End If

End Sub

なお、Office 2007以前にはLongPtr型自体が存在しないため、#If VBA7#Else側では変数宣言もLongにする必要がある点に注意してください。

実践例:Sleep関数で一定時間待機する

Windows APIのSleep関数を使うと、Application.Waitより短い単位(ミリ秒)でマクロを待機させることができます。32bit・64bit両対応の宣言と組み合わせた完成形は次の通りです。

#If VBA7 Then
    Private Declare PtrSafe Sub Sleep Lib "kernel32" ( _
        ByVal dwMilliseconds As Long)
#Else
    Private Declare Sub Sleep Lib "kernel32" ( _
        ByVal dwMilliseconds As Long)
#End If

Sub WaitHalfSecond()

    Debug.Print "待機開始: " & Now
    Sleep 500  ' 500ミリ秒(0.5秒)待機
    Debug.Print "待機終了: " & Now

End Sub

Sleepの引数(待機ミリ秒数)は32bit・64bitどちらでもLong型のままで問題ありません。ポインタやハンドルを扱わないAPIであれば、PtrSafeさえ付ければ既存のコードをほぼそのまま流用できます。

既存の32bit専用コードを64bit対応に書き換える手順

古いサンプルや書籍のコードをそのまま使うと、PtrSafeのないDeclareステートメントが原因で64bit環境で動かないことがあります。書き換える際は、次の手順で確認すると安全です。

  1. Declare Function / Declare Sub の直後にPtrSafeが付いているか確認する
  2. 戻り値やハンドルを受け取る引数の型がLongになっていないか確認し、必要ならLongPtrに変更する
  3. 32bit環境(Office 2007以前)でも使う可能性があるなら、#If VBA7で分岐させる
  4. 変更後は32bit・64bit両方の環境、または少なくとも自分の環境で実行して動作確認する

社内で配布するマクロの場合、利用者の環境が32bit版Officeか64bit版Officeか分からないことも多いため、基本的には#If VBA7による分岐を入れておくと安全です。

まとめ

PtrSafeは64bit版Officeで安全にWindows APIを呼び出すためのキーワードで、Office 2010以降のDeclareステートメントでは必須です。ポインタやハンドルを扱う引数・戻り値はLongではなくLongPtr型を使い、#If VBA7による条件付きコンパイルを組み合わせることで、32bit・64bit両方の環境で動作するマクロを1つのコードで管理できます。

古いAPI宣言コードを流用する際は、まずPtrSafeの有無と型を確認する習慣をつけておくと、環境依存のエラーに悩まされにくくなります。

スポンサーリンク
スポンサーリンク
ExcelVBA
シェアする
いがぴをフォローする

コメント

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