WindowsAPI をOffice64bit版または32bit版のVBAで使うには

2019年6月22日VBA全般

To use Windows API with Office 64bit or 32bit VBA

いままでOfficeの32bit版のVBAでWindowsAPIを使用していたコードが、Officeを64bit版に変更するとコンパイルエラーになり使えないということが発生します。64bit版に対応させるためには、WindowsAPIのDeclareステートメントの宣言を書き換える必要があります。WEB検索するとMS公式のものも含めていろいろ情報が見つかりますが、分かりにくかったり、不正確な情報も多々あります。そこでhatenaなりに分かり安く整理してみました。

Office64bit版、Office32bit版どちらでも使用できるDeclare宣言

詳しい解説は後回しにして、結論を先に書きます。

Office2010以降(VBA7)の場合

Declare宣言

実例で説明したほうか分かり安いと思いますので、SetWindowPos というAPIを例に説明します。

旧来の宣言は下記になります。

Public Declare Function SetWindowPos Lib "user32" _
                        (ByVal hwnd As Long, _
                         ByVal hWndInsertAfter As Long, _
                         ByVal X As Long, ByVal Y As Long, _
                         ByVal cx As Long, ByVal cy As Long, _
                         ByVal wFlags As Long) As Long

これを、64bitにも対応させるには、下記のように書き換えます。

Public Declare PtrSafe Function SetWindowPos Lib "user32" _
                                (ByVal hwnd As LongPtr, _
                                 ByVal hWndInsertAfter As LongPtr, _
                                 ByVal X As Long, ByVal Y As Long, _
                                 ByVal cx As Long, ByVal cy As Long, _
                                 ByVal wFlags As Long) As Long

変更箇所は、下記の2点です。

  • Declare の後に PtrSafe を挿入する。
  • ウィンドウハンドル、ポインタを表す関数の引数、戻り値の型は、Long型からLongPtr型に変更する。

これで、64bitでも32bitでも動作します。(Office2010以降でも 条件付きコンパイル を使ってかき分けるコードを提示しているサイトがありますが、かき分ける必要はありません。)

どの引数をLongPtr型に変更すればいいか分からないという場合は、下記のMSサイトからダウンロードしたファイルを実行するとCドライブC:\Office 2010 Developer Resources\Documents\Office2010Win32API_PtrSafe フォルダーに Win32API_PtrSafe.TXT ができます。そこに64bit対応のAPI宣言が網羅されてますので、それを開いて検索するといいでしょう。

それが面倒だという場合は、下記に Win32API_PtrSafe.TXT を置いておきましたので、クリックしてブラウザの検索機能で検索すると簡単です。

https://hatena19.com/Office2010Win32API_PtrSafe/Win32API_PtrSafe.TXT

上記のリンクを開いて、「SetWindowPos」で検索すると Declare文がヒットしますのでそれをコピペすればOKです。

さらに検索で次の候補に SetWindowPos で使用する定数宣言もヒットするので、これもコピペしておきます。(APIによってない場合もあります。)

' SetWindowPos Flags
Const SWP_NOSIZE = &H1
Const SWP_NOMOVE = &H2
Const SWP_NOZORDER = &H4
Const SWP_NOREDRAW = &H8
Const SWP_NOACTIVATE = &H10
Const SWP_FRAMECHANGED = &H20        '  The frame changed: send WM_NCCALCSIZE
Const SWP_SHOWWINDOW = &H40
Const SWP_HIDEWINDOW = &H80
Const SWP_NOCOPYBITS = &H100
Const SWP_NOOWNERZORDER = &H200      '  Don't do owner Z ordering

Const SWP_DRAWFRAME = SWP_FRAMECHANGED
Const SWP_NOREPOSITION = SWP_NOOWNERZORDER

' SetWindowPos() hwndInsertAfter values
Const HWND_TOP = 0
Const HWND_BOTTOM = 1
Const HWND_TOPMOST = -1
Const HWND_NOTOPMOST = -2

64bit版と32bit版で宣言を変更する必要があるAPI

通常は64bit版と32bit版で共通の宣言でいいのですが、例外的に分ける必要があるAPIがあります。例えば、 Win32API_PtrSafe.TXT で GetWindowLongPtr を検索すると、下記がヒットします。

#If Win64 Then
Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongPtrA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
'中略
#Else
Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As LongPtr, ByVal nIndex As Long) As LongPtr
'中略
#End If

#If ... Then というのは、条件付きコンパイルというもので、コンパイル時に条件に合ったものだけ有効にするというものです。#If Win64 Then で64bit版の時のみ有効という意味になります。つまり下記ということです。

#If Win64 Then
'64版のときのみ有効なコード
#Else
'32版のときのみ有効なコード
#End If

よって、上記のコードは GetWindowLongPtrA は64bitのみでしか使えないものなので、32bitでは GetWindowLongA を代用するという意味になります。

このような例外的なものも Win32API_PtrSafe.TXT で分かります。

以上で、Office2010以降のVBAでAPIを使う場合の話は終了です。2007以前は使用しないという場合は以下は読む必要はありません。

Office2007以前の場合

Office2007は2017年に延長サポートも既に終了しています。サポート終了のものを使い続けるのは、セキュリティ上非常に危険なのでやめるべきです。しかし、どうしても使わざるをえない事情がなるなら、条件付きコンパイルを使って2007以前に対応できるように宣言する必要があります。これも、SetWindowPos を例に、結論から書くと、

#If VBA7 Then
    Public Declare PtrSafe Function SetWindowPos Lib "user32" _
                                    (ByVal hwnd As LongPtr, _
                                     ByVal hWndInsertAfter As LongPtr, _
                                     ByVal X As Long, ByVal Y As Long, _
                                     ByVal cx As Long, ByVal cy As Long, _
                                     ByVal wFlags As Long) As Long
#Else
    Public Declare Function SetWindowPos Lib "user32" _
                             (ByVal hwnd As Long, _
                              ByVal hWndInsertAfter As Long, _
                              ByVal X As Long, ByVal Y As Long, _
                              ByVal cx As Long, _
                              ByVal cy As Long, _
                              ByVal wFlags As Long) As Long
#End If

となります。#Else の後には、PtrSafe を削除して、LongPtrLong に変更したものを記述します。

#If vb7 Then の意味は、VBAのバージョンが 7.0 以降という意味になります。Office2010以降に付属してくるVBAは7.0以降になります。VBA7には64bit版と32bit版があります。2007以前はVBA6になります。これは32bit版のみです。

ネットの情報をみると、#If win64 Then を使ったり、 #If VBA7 And Win64 then で切り分けるというコードをみかけます。これでもエラーなく動作はします。

しかし、#If VBA7 And Win64 then は、VBA7.0以降かつ64bit版という意味になりますが、64bit版はVBA7.0以降しかないで冗長な条件です。また、#If win64 Then は、上で提示した 64bit版と32bit版で異なる宣言が必要な場合に使用するので、このような用途(VBA6に対応させる)では使用すべきではないです。

つまり、下記のように使い分けるべきであるということです。

#If VBA7 Then
    'Office2010以降で64/32bit共通の宣言
    If Win64 Then
        ' 64bit版用の宣言(分ける必要がある場合のみ)
    #Else
        ' 32bit版用の宣言(分ける必要がある場合のみ)
    #End If
#Else
    'Office2007以前用の宣言
#End If

なぜ、VBA7では64bit版と32bit版で共通の宣言でいいのか

ここからは、ややこしいので興味のある方のみ読んでいただければ結構です。

VBA7ではAPIを

Public PtrSafe Declare Function SetWindowPos Lib “user32” (・・・

と宣言しますが、これは、“user32" というライブラリ内の SetWindowPos という関数を使えるようにする、という意味になります。

Declare PtrSafe Function GetWindowLongPtr Lib "user32" Alias "GetWindowLongPtrA" (・・・

は、“user32" というライブラリ内の GetWindowLongPtrA という関数を、GetWindowLongPtrという別名で使えるようにする、という意味になります。

“user32"ライブラリは何かというと、Cドライブ(OSのインストールしてあるドライブ)の Windows\System32フォルダ内にある user32.dll というファイルが実体です。(APIのdllファイルは他にもあります。)

Public Declare Function SetWindowPos Lib “C:\Windows\System32\user32.dll” (・・・

とフルパスで記述するところを Windows\System32フォルダはパスか通っているので省略することができます。

32bitOSの場合は、32bit版のAPI(dllファイル)が上記のフォルダーにあります。では、64bitOSの場合はというと、やはりWindows\System32フォルダに64bit版のAPI(dllファイル)があります。64bitなのになぜかSystem32です。たぶん互換性のためにそうしたのでしょう。

64bitOS上の64bitアプリケーションから使う場合はここを参照すればいいです。では、64bitOS上の32bitアプリケーションからAPIを使用するにはどうしましょうか。32bitアプリケーションから、64bit版APIは使えるのでしょうか。答えは否です(ネット情報ではできるとしている情報もあるが間違いです)

64bitOSでは32bit版のdllファイルは、Windows\SysWOW64 にあります。32bitなのに64、なんとややこしい。WOW64 とは何かというと、正式名称は Windows 32bit emulation on Windows 64bit64bitOS上で32bitアプリケーションを動かすための仕組みということです。簡単な説明は下記を、

もっと詳しい説明をという場合は、下記を参照。

32bit版OSをエミュレーションするための環境を作成してそこで32bitアプリケーションを実行するという複雑なことをしています。

で、32bitアプリケーションからSystem32フォルダのライブラリを参照すると、WOW64 という仕組みがSYSWOW64フォルダへリダイレクト(誘導)してくれるわけです。ですので、64bitアプリから64bit版のAPIが、32bitアプリからは32bit版のAPIが呼び出され、どちらも正常に使用できるわけです。

Public Declare PtrSafe Function SetWindowPos Lib "user32" _
                                (ByVal hwnd As LongPtr, _
                                 ByVal hWndInsertAfter As LongPtr, _
                                 ByVal X As Long, ByVal Y As Long, _
                                 ByVal cx As Long, ByVal cy As Long, _
                                 ByVal wFlags As Long) As Long

の PtrSafe は64bit版にも対応してますよという宣言で、64bitVBAではこれがないとコンパイルエラーになります。32bitVBAでは意味は持たないので、ないのと同意になります。LongPtr は64bitVBAでは8 バイトの数値だか、32bitVBAでは4バイトの数値です。4バイトの数値とはすなわちLong型です。

LongPtrは、32 ビット環境ではLongに変換され、64 ビット環境ではLongLongに変換される

LongPtr データ型 | Microsoft Docs

ということは、上記の宣言は、32bitVBA上では、PtrSafe は無視され、LongPtr は Long に変換されるので、

Public Declare Function SetWindowPos Lib "user32" _
                        (ByVal hwnd As LongPtr, _
                         ByVal hWndInsertAfter As LongPtr, _
                         ByVal X As Long, ByVal Y As Long, _
                         ByVal cx As Long, ByVal cy As Long, _
                         ByVal wFlags As Long) As Long

と同義ということです。ですので、32bitVBA上でエラーなく使用できます。

まとめ

  • Officeの64bit版、32bit版の両方に対応するAPI宣言は、Declare の後に PtrSafe を挿入し、ウィンドウハンドル、ポインタを表す関数の引数、戻り値はLongPtr型にする。
  • Win32API_PtrSafe.TXTに64bit/32bit両対応のAPI宣言が網羅さているのでそれを参照するとよい。
  • ただし、Office2007以前には対応していない。サポート終了しているOffice2007以前は使うべきではないが、やむをえず使わざるを得ない場合は、条件付きコンパイル #If VBA7 Then … #Else … #End を使ってかき分ける。

VBA全般VBA,WindowsAPI

Posted by hatena