64ビット版 Excel で「PtrSafe 属性を設定してください」のコンパイルエラーが出るときの直し方
古いマクロを64ビット版の Excel で開いたときに出る、Declare ステートメントのコンパイルエラーについて、PtrSafe と LongPtr を使った直し方と、32ビット版・64ビット版の両方で動かす書き方を解説します。
どんなエラーか
マクロを実行しようとすると、「このプロジェクト内のコードを 64 ビット システムで使用するように更新する必要があります。Declare ステートメントを確認、更新して、PtrSafe 属性を設定してください。」というコンパイルエラーが出て、Declare で始まる行が赤くなる状態です。
Declare は、Windows の機能(API)を VBA から呼び出すための宣言です。昔の32ビット版 Excel 向けに書かれたマクロを、64ビット版の Excel で開くと発生します。最近の PC は64ビット版の Office が標準のため、PC の入れ替えのタイミングでよく起きます。
自分の Excel が64ビット版か確認する
Excel の「ファイル」→「アカウント」→「Excel のバージョン情報」を開くと、最初の行の最後に「32 ビット」または「64 ビット」と表示されます。マクロを使う人の PC がどちらなのかを、先に確認しておきます。
直し方:PtrSafe を付け、ハンドルやポインタを LongPtr にする
Declare の後ろに PtrSafe を付けます。あわせて、ウィンドウのハンドルやメモリの位置(ポインタ)を表す引数・戻り値の型を、Long から LongPtr に変えます。LongPtr は、32ビット版では Long、64ビット版では LongLong として扱われる型です。
注意が必要なのは、PtrSafe を付けるだけでもエラーは消えることです。ハンドルの型を Long のままにすると、64ビット版では値が収まらず、Excel が強制終了するなどの不具合につながります。どの引数がハンドルなのかは、Microsoft の API のリファレンスで確認します。
' 修正前(32ビット版向け)
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As Long
' 修正後(Sleep の引数は時間なので Long のまま、FindWindow の戻り値はハンドルなので LongPtr)
Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr古い Excel でも動かす必要がある場合
Excel 2007 以前(VBA6)では PtrSafe と LongPtr が使えません。古い Excel を使う人がいる場合は、#If VBA7 で書き分けます。VBA7 は Excel 2010 以降で真になります。
#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 IfAPI を使わない書き方に置き換える方法もある
待ち時間を作るための Sleep のように、Excel の機能で代わりができるものは、API をやめてしまうのも一つの方法です。宣言自体がなくなるため、32ビット版と64ビット版の違いを気にする必要がなくなります。
- Sleep の代わりに Application.Wait Now + TimeValue("00:00:02") を使う(秒単位)
- クリップボードの操作は、API の代わりに DataObject や Range の値の代入で行う
- ファイル選択のダイアログは、API の代わりに Application.FileDialog を使う
※API の宣言を誤ると、エラーにならずに Excel が強制終了することがあります。修正したマクロは、大事なブックを閉じた状態で、テスト用のブックで動作を確認してください。