Excelはバージョン互換が高いアプリケーションなので、VBAでAPIを含む記述が無い場合ならそのまま正常に動作してくれます。ただし、APIを使用したVBAの記述がある場合については互換性がないのですべて次のようなエラーになります。
正常に動作させるためには、64ビット版Excelに対応できるよう記述を書き換える必要があります。
【この記事でわかることは】
・64ビット版Excel(Office)でもWin32APIを使えるように修正する方法
・条件付きコンパイルの設定方法
はじめに
この記事の中で「Win32APIの64ビット対応」について簡単に書いています。
その後、過去の記事でWin32APIを使っているものについてコードの修正を行いました。その際に、64ビット版ExcelのVBAでWin32APIを使う場合の方法については、もう少し具体的に書いておいたほうが良いと思った次第です。
64ビット版のExcel(Office)
2021年6月から「Microsoft 365」の年間ライセンスに変更していますが、Excel(Office)は32ビット版を使っていました。OSはWindows10(64ビット)のままという環境を継続していました。
実は、先日新しいPC(OSはWindows11)を1台購入したタイミングで、64ビット版のExcel(Office)をインストールしました。※複数のPCにインストール可能ですが同時利用の制限があります。
64ビット版Windowsには「WOW64」という32ビット用のプログラムを動作させるための仕組みが用意されています。(「WOW64」とは「Windows 32-bit On Windows 64-bit」の略)
64ビット版Windowsの「C:\Windows\SysWOW64」フォルダに32ビット版Windowsの「System32」フォルダの中身が入っています。これがあるので、32ビット版のアプリケーションを64ビット版Windowsにインストールできています。32ビット版Excel(Office)が64ビット版Windows上で問題なく動作できるのはこの仕組みのおかげです。
WindowsとExcel(Office)のバージョンによる動作可否は下表のとおりです。
ただし、64ビット版のWindows上では32ビット版Officeと64ビット版Officeのどちらも選択できますが、残念ながら両方を共存させることはできません。
WindowsのAPIについては一部の例外を除いて記述修正で済みますが、サードパーティーのDLLやCOMなどで32ビット環境しか対応していない場合はこれを利用するものは実行できないのでVBAマクロの有無に関係なく注意が必要です。このような場合は64ビット環境に対応したバージョンを用意する必要があります。※ 7-zip32.dll → 7-zip64.dll というような例です。
Declareステートメントを64ビット用に記述修正
実際に過去記事「【ExcelVBA】ファイルのタイムスタンプを一括で指定変更する」のコードで使ったWin32APIについて実際に修正していきます。
PtrSafe を含む Declare ステートメントに修正しておけば、64ビットと32ビットの両方のExcelで動作できるようになります。
Win32API_ptrsafe.txt を用意します
Win32APIの修正を正しく行うには、Win32API_ptrsafe.txt 内を検索し該当のものに差し替えます。
また、下の引用のとおりTypeメンバーの型宣言なども含まれていますので併せて修正します。
Win32API_ptrsafe.txt の内容については次の引用のとおりです。
Win32API_PtrSafe.txtには、次のものが含まれます。
https://learn.microsoft.com/ja-jp/office/troubleshoot/office-suite-issues/win32api_ptrsafe-with-64-bit-support?source=recommendations
· 元のWin32API.txt ファイルに含まれていたWindows API 関数の 32 ビット (x86) および 64 ビット (x64) 互換の Declare ステートメント。
· 指定された Declare ステートメントで使用される定数のグローバル定数宣言。
· 指定された Declare ステートメントで使用されるユーザー定義型 (構造体) の型宣言。Win32API_PtrSafe.txtをダウンロードするには、Office 2010 ヘルプ ファイル: Win32API_PtrSafe 64 ビット サポートの Web ページからファイル Office2010Win32API_PtrSafe.exeにアクセスします。
ダウンロード “Office2010Win32API_PtrSafe.exe” では、”Win32API_PtrSafe.txt”、”using Win32API_PtrSave.xps”、”UsingWin32API_PtrSafe.docx” がインストールされます。 後者の 2 つのファイルには、VBA コードを記述するときにWin32API_PtrSafe.txtの内容を使用するための重要な情報が含まれています。
目的の Win32API_PtrSafe.txt はインストール先のフォルダ
「C:\Office 2010 Developer Resources\Documents\Office2010Win32API_PtrSafe」内にインストールされます。記事内でリンク参照できるように Win32API_PtrSafe.txt を登録しておきました。
Win32API_PtrSafe.txt には、Win64用のものとWin32用の両方が含まれています。条件分岐されている場合がありますので「#If Win64」に記述されているものはWin64用です。「#Else」以降がWin32ですので間違えないように注意してください。※下図画像参照
Declareステートメントを差し替える
「【ExcelVBA】ファイルのタイムスタンプを一括で指定変更する」で使用したAPIの一部抜粋です。
Private Declare Function CreateFile Lib "kernel32.dll" Alias "CreateFileA" ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long _
) As Long
Win32API_PtrSafe.txtを開き「Function CreateFile Lib」で検索した結果画像がこちらです。
該当行をコピーしたものがこちらです。内容を比較して変更点を赤字にしています。
Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileA" ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
lpSecurityAttributes As SECURITY_ATTRIBUTES, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As LongPtr _
) As LongPtr
このコードに差し替えますが、「lpSecurityAttributes As SECURITY_ATTRIBUTES」の部分は元のコード「ByVal lpSecurityAttributes As Long」から「SECURITY_ATTRIBUTES」構造体を参照するようになっています。では「SECURITY_ATTRIBUTES」を検索してみます。
Typeメンバーの型宣言が見つかりました。この部分もそのままコードにコピペします。
このように「Declareステートメント」や「グローバル定数宣言」「ユーザー定義型 (構造体) の型宣言」部分を検索して、そのままコピペして差し替えたり必要に応じて追加したりしていきます。
型の名称も変更されているので、そのままコピペするのがよいでしょう!
VBEでコンパイルして確認します
設定後にVBEでコンパイルします。(VBEメニュ>コンパイル>VBAProjectのコンパイル)
エラーが出なければOKですが下の画像のように「コンパイルエラー」が出た場合は、変数名やデータ型などを修正していきます。
先ほど修正した「lpSecurityAttributes As SECURITY_ATTRIBUTES」と追加した「SECURITY_ATTRIBUTES」構造体の部分でもエラーがあったので修正しました。
ByVal lpSecurityAttributes As Long のままで正しく動作確認できたので修正しました。
最終的に修正完了したコードが次のとおりです。
すべて検索して書き換えたコード
Option Explicit
'オブジェクトへのアクセスの種類を指定する定数の宣言
Const GENERIC_READ = &H80000000
Const GENERIC_WRITE = &H40000000
Const FILE_SHARE_READ = &H1
Const FILE_ATTRIBUTE_NORMAL = &H80
Const OPEN_EXISTING = 3 'ファイルへの動作を指定する定数の宣言
'ファイルなどの作成やオープンや切り捨てを行う関数の宣言
'オブジェクトをアクセスするために利用できるハンドルを返す
Declare PtrSafe Function CreateFile Lib "kernel32" Alias _
"CreateFileA" (ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As LongPtr _
) As LongPtr
'lpSecurityAttributes As SECURITY_ATTRIBUTES ← Longに変更
'Private Type SECURITY_ATTRIBUTES
' nLength As Long
' lpSecurityDescriptor As LongPtr
' bInheritHandle As Long
'End Type
'オープンされているオブジェクトハンドルをクローズする関数の宣言
Declare PtrSafe Function CloseHandle Lib "kernel32" Alias _
"CloseHandle" (ByVal hObject As LongPtr) As Long
'ファイル時間をシステム時間に変換する関数の宣言
Declare PtrSafe Function LocalFileTimeToFileTime Lib "kernel32" Alias _
"LocalFileTimeToFileTime" (lpLocalFileTime As FILETIME, _
lpFileTime As FILETIME) As Long
'ファイル時間を定義する構造体
Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
'システム時間をファイル時間に変換する関数の宣言
Declare PtrSafe Function SystemTimeToFileTime Lib "kernel32" Alias _
"SystemTimeToFileTime" (lpSystemTime As SYSTEMTIME, _
lpFileTime As FILETIME) As Long
'システム日時を格納する構造体
Type SYSTEMTIME
wYear As Integer
wMonth As Integer
wDayOfWeek As Integer
wDay As Integer
wHour As Integer
wMinute As Integer
wSecond As Integer
wMilliseconds As Integer
End Type
'ファイルの作成日時などを設定する関数の宣言(最終アクセス、最終更新)
Declare PtrSafe Function SetFileTime Lib "kernel32" Alias _
"SetFileTime" (ByVal hFile As LongPtr, _
lpCreationTime As FILETIME, _
lpLastAccessTime As FILETIME, _
lpLastWriteTime As FILETIME) As Long
'パラメータ
'hFile 日時を設定するファイルのハンドルを指定
'lpCreationTime 作成日時を保持(FILETIME構造体へのポインタ)
'lpLastAccessTime 最終アクセス日時(FILETIME構造体へのポインタ)
'lpLastWriteTime 最終更新日時(FILETIME構造体へのポインタ)
'戻り値 関数が成功すると、0 以外の値が返る(失敗すると0)
条件付きコンパイルを設定
前回も説明しましたが、2つの条件付きコンパイル定数があります。
・VBA7 という定数は、Excelのバージョンが2007以前か2010以降かを区別します。
・Win64 という定数は、実行しているExcelが64ビットなのか32ビットなのかを区別します。
この定数を使って、コンパイル時に「#IF…Then…#Else…#End If」ディレクティブで条件分岐します。こうすることで該当した場合だけその部分のコンパイルを実行してくれます。
次の画像は、前回(7-zip.dll)、Excelが64ビット版か32ビット版かで分岐させる必要があったので Win64 だけ使用した例です。
VBEでコードを見ると上の画像のようにコンパイルしない部分のコードが「赤字」で(コンパイルエラーになる部分として)表示されます。
Excelが64ビット版なのかを判定する必要がない場合は VBA7 を使います。
両方判定する必要がある場合は、次の例のようにネストして判定・分岐します。
条件付きコンパイルに使うコード例
Option Explicit
'条件付きコンパイル定数でAPI宣言を分岐
#if VBA7 then
'Excelのバージョンが2010以降(VBA7)の場合
#if Win64 then
'64ビット版Excelだけで使用する場合はここに入れます
Declare PtrSafe Function ......
#else
'64ビットと32ビット共用できる新API宣言はここに入れます
Declare PtrSafe Function ......
#end if
#else
'Excelのバージョンが2007以前(VBA7以前)の場合(32ビットの旧API宣言)
Declare Function ......
#end if
API呼び出し側の変数宣言も忘れずに修正
API宣言で修正した部分については、呼び出し側も合わせるように修正しました。
「コンパイル」作業を行った際にコンパイルエラー「型が一致しません」と表示された部分です。
'ファイルのハンドルを取得
Private Function GetFileHandle(ByVal strFilePath As String) _
As LongPtr '← Longから変更
GetFileHandle = CreateFile( _
strFilePath, GENERIC_READ Or GENERIC_WRITE, _
FILE_SHARE_READ, 0, OPEN_EXISTING, _
FILE_ATTRIBUTE_NORMAL, 0 _
)
End Function
Dim cFileHandle As LongPtr '← Longから変更
'ファイルのハンドルを取得する
cFileHandle = GetFileHandle(strFilePath)
・「CreateFile」の戻り値が LongPtr なので GetFileHandle の型も LongPtr に変更しました。
・変数 cFileHandle の型も GetFileHandle の型に合わせて LongPtr に変更しています。
・このように修正しなければ、コンパイルエラー「型が一致しません」が出てしまいます。
まとめ(おわりに)
64ビット版と32ビット版どちらのExcelからでも Win32API を使用できるようにする方法について解説しました。
今回の記事はいかがでしたか? お役に立てたなら幸いです(^^;
修正したWin32APIを使ったサンプルを追加登録しました
各ファイルは次のダウンロードページへのリンク先に登録しています。
・【ExcelVBA】Keybd_eventでスリープを阻止するコード検証
・【ExcelVBA】ファイルのタイムスタンプを一括で指定変更する
・Excel VBA「入力規則」外部リンクエラーの自動削除ツール
・Excel VBA プログレスバーをラベルで代用表示させる方法
・Excel VBA プログレスバーを動的に作成>表示>廃棄する
ダウンロードページへトップリンクは下のカードクリックでジャンプできます。
よろしければご利用ください!