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環境で動かないことがあります。書き換える際は、次の手順で確認すると安全です。
Declare Function/Declare Subの直後にPtrSafeが付いているか確認する- 戻り値やハンドルを受け取る引数の型が
Longになっていないか確認し、必要ならLongPtrに変更する - 32bit環境(Office 2007以前)でも使う可能性があるなら、
#If VBA7で分岐させる - 変更後は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の有無と型を確認する習慣をつけておくと、環境依存のエラーに悩まされにくくなります。

コメント