VBAで自作DLLファイルを動的に呼び出す|C++でDLLを自作してExcelVBAで呼び出す方法⑤

第2回、第3回、第4回を通して関数の基本となる引数と戻り値の設定方法を解説してきたため、C++の知識さえあればVBAと連携可能な関数はある程度何でも作成できるような状態となりました。
しかし、これまでのコードはVBAで自作DLL関数を呼び出すために、呼び出し元であるDLLファイルの”フルパス”を毎回丁寧に書いてあげる必要がありました。正直なお話、DLLファイルの場所を移動しただけで使えなくなってしまうのは面倒ですし、もう少し融通の利くような設定にしておきたいです。
そこで、今回は自作したDLLファイルを動的に呼び出す方法を解説していきます。これにより”Excelファイル(.xlsm)の存在するディレクトリ”というような動的な場所を設定することが可能になります。
DLLファイルを動的に呼び出す(ファイル指定)
DLLファイルはWindowsAPIの「LoadLibrary関数」を使うことで読み込むことが出来ます。
下記はLoadLibrary関数を使ってExcelファイル(.xlsm)と同階層にあるDLL(VBA_DLL.dll)を読み込み、前回作成した「GetFileSize1関数」を呼び出すサンプルコードです。これまでフルパスで指示していたDLL名の部分はWindowsAPIのようにファイル名だけの記載とすることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
'動的にDLLを取得するためのWinAPI Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr Private Declare PtrSafe Function GetProcAddress Lib "kernel32" (ByVal hModule As LongPtr, ByVal lpProcName As String) As LongPtr Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As LongPtr) As Long 'DLLファイルの関数定義 Private Declare PtrSafe Function GetFileSize1 Lib "VBA_DLL.dll" (ByVal sFilePath As String) As Long Sub main() Dim hDll As LongPtr Dim hProc As LongPtr Dim sFolderPath As String Dim iFileSize As Long Dim sFilePath As String 'DLLファイルを保存するフォルダパスを設定 sFolderPath = ThisWorkbook.Path 'DLLファイルを読み込む hDll = LoadLibrary(sFolderPath & "\" & "VBA_DLL.dll") '"DLLファイルフルパス 'DLLファイルの指定関数のアドレス取得 hProc = GetProcAddress(hDll, "GetFileSize1") If hProc = 0 Then Call MsgBox("DLLファイルの読み込みに失敗しました") Call FreeLibrary(hDll) Exit Sub End If 'ファイル選択ダイアログ表示 sFilePath = Application.GetOpenFilename() If sFilePath = "False" Then Exit Sub 'DLLの関数に選択されたファイルパスを入力して実行 iFileSize = GetFileSize1(sFilePath) MsgBox "選択したファイルのサイズは" & iFileSize / 1024 & "kbです" 'DLL解放 Call FreeLibrary(hDll) End Sub |
コード解説
指定のDLLファイルを読み込むにはLoadLibrary関数を使い、下記のように記載します。
hDll = LoadLibrary(lpLibFileName)
引数のlpLibFileNameにDLLファイルのフルパスの入力することで、指定のDLLファイルをロードされ、戻り値としてそのDLLへのハンドルを取得することができます。この処理によりDLLファイル内にある関数を使用することが可能になります。
DLLが不要になった場合は、FreeLibrary関数を使いDLLを解放する必要があります。引数のlpLibFileNameには読み込み済みのDLLへのハンドル(LoadLibrary関数の戻り値)を入力します。
呼び出したアプリケーション(今回の場合はExcel)を終了すると読み込んだDLLファイルは自動的にアンロードされますが、アプリケーションを閉じるまではアンロードされないため、FreeLibrary関数を使って明示的にアンロードさせています。(詳細はLoadLibrary関数の概要ページを参照下さい)
動的な読み込みとしては上記2つの関数のみで対処可能です。このときDLLの指定の関数が正常に読み込めなかった場合での条件分岐をする場合にはGetProcAddress関数を使い下記のように記載します。
第1引数のhModuleには対象のDLLへのハンドル、第1引数のlpProcNameには実行したい関数名をそれぞれ入力します。このとき入力したDLL内にその関数が存在する場合は戻り値としてその関数へのアドレスが返ってきますが、関数が存在しない場合は戻り値が「0」となります。これによりDLLの指定の関数が正常に読み込めているのかの判定をすることができます。
DLLファイルを動的に呼び出す(フォルダ指定)
前項のLoadLibrary関数は指定のDLLファイルを読み込むものなので、複数のDLLファイルを読み込ませたい場合はそのファイル数分だけ読み込ませる処理を書く必要があります。それに対して指定のフォルダ内にあるDLLファイルを一括で読み込ませる方法もあります。ただし前提条件として管理者権限があるユーザーに対してのみ有効な方法なので配布する場合は注意が必要です。
指定のフォルダ内のDLLを一括で読み込ませるにはWindowsAPIの「AddDllDirectory関数」を使います。下記はAddDllDirectory関数を使ってExcelファイル(.xlsm)が存在するフォルダ内のDLLを読み込むためのサンプルコードです。見た目上の処理は前項のサンプルコードと同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
'動的にDLLを取得するためのWinAPI Private Declare PtrSafe Function SetDefaultDllDirectories Lib "kernel32.dll" (ByVal DirectoryFlags As Long) As Long Private Declare PtrSafe Function AddDllDirectory Lib "kernel32.dll" (ByVal fileName As String) As LongPtr 'DLLファイルの関数定義 Private Declare PtrSafe Function GetFileSize1 Lib "VBA_DLL.dll" (ByVal sFilePath As String) As Long Const LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = &H1000 ':0x00001000 Sub main() Dim DLLFoldPath As String Dim iFileSize As Long Dim sFilePath As String 'DLLファイルを保存するフォルダパスを設定 DLLFoldPath = ThisWorkbook.Path '動的にDLLファイルを取得 If SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_DEFAULT_DIRS) = 0 Then Exit Sub If AddDllDirectory(StrConv(DLLFoldPath, vbUnicode)) = 0 Then Exit Sub sFilePath = Application.GetOpenFilename() If sFilePath = "False" Then Exit Sub iFileSize = GetFileSize1(sFilePath) MsgBox "選択したファイルのサイズは" & iFileSize / 1024 & "kbです" End Sub |
コード解説
DLLファイルをフォルダ指定で呼び出すにはAddDllDirectory関数と、AddDllDirectory関数を使うためのSetDefaultDllDirectories関数を使います。
DLLファイルは呼び出す際に、環境変数のPathとして設定されているディレクトリをすべて探索して該当のファイルを取得するというような処理が行われます。AddDllDirectory関数はこのDLLファイルの探索範囲に、指定したパスを一時的に追加することができる関数です。そしてSetDefaultDllDirectories関数はこのAddDllDirectory関数の機能を有効にするための関数です。
コードとしてはまずはじめにDLLファイルを呼び出すときに探索するディレクトリの範囲をSetDefaultDllDirectories関数を使って変更します。本関数の構文は下記の通りです。
lRet = SetDefaultDllDirectories(DirectoryFlags)
引数のDirectoryFlagsは4つの定数となっておりそれぞれ下表の通りです。
値 | 内容 |
LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x00000200 (VBA: &H1000) |
アプリケーションのインストールディレクトリが探索範囲となる。 |
LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400 (VBA: &H400) |
AddDllDirectory または SetDllDirectory 関数で追加されたパスが探索範囲となる。 |
LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 (VBA: &H800) |
system32フォルダ(%windows%\system32)が探索範囲となる。 |
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS 0x00001000 (VBA: &H1000) |
上記3つのすべての範囲が探索範囲となる。 |
AddDllDirectory関数を使うためには、上記のうち「LOAD_LIBRARY_SEARCH_USER_DIRS」もしくは「LOAD_LIBRARY_SEARCH_DEFAULT_DIRS」のいずれかの値を引数として渡す必要があります。どの引数も16進数のため、VBAで利用する際は「&H」を使って16進数化する必要があるので注意しましょう。戻り値は関数が失敗したら「0」、成功したら「0以外の整数値」が返ってきます。
上記の通りDLLファイルの探索範囲としてAddDllDirectory関数で設定したパスを探索するような設定ができたら、下記のように記載して対象のフォルダを探索範囲として追加します。
lRet = AddDllDirectory(StrConv(DLLFoldPath, vbUnicode))
引数は1つでDLLファイルを探索するディレクトリのパスを入力します。ここで注意しないといけないのは引数として渡す文字列をUnicodeに変換する必要があるという点です。これはVBAの「StrConv関数」で簡単に変換することが出来るため、上記のように変換した値を入力する必要があります。戻り値としては関数が失敗したら「0」、成功したら「0以外の整数値」がLong型で返ってきます。
AddDllDirectory関数実行後、指定のフォルダ内にある「Declare PtrSafe ~」で宣言している対象のDLLファイルが読み込まれ、その中にある関数を呼び出すことが可能になります。
WindowsAPIとDLLファイル
これまでの内容が理解できた方であればWindowsAPIとは何なのかをより理解することができます。
「Windows APIを使う」ということは「Windowsが標準装備として持っているDLLファイル内にある関数を呼び出す」ということを意味しています。このときVBAで自作DLLファイルを呼び出すときとは違いWindows APIの関数の呼び出しではDLLのフルパスを書く必要はありません。
たとえばSleep関数でいえば、下記のように書くことで関数の使用が可能になりますが、DLLファイルである「kernel32.dll」の所在となるフルパスはどこにも記載していません。
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
これはWindows APIと呼ばれる関数を持つDLLファイルら(kernel32.dllなど)の存在しているディレクトリのパスが、Windowsの標準設定により”通された”状態になっているためです。このパスが通されたという状態は、「環境変数のPathとして設定された」と言い換えてもよいです。
環境変数とは簡単にいえば、どのプログラムからでもアクセスできるように設定されているコンピュータ自身が持っている情報です。VBAでDLLファイルを呼び出す際はこの環境変数のPathとして設定されているすべてのパスの中から、指定した名称のファイルが探索されます。
「kernel32.dll」は「system32」というフォルダに保存されており、この「system32」は環境変数として設定されています。つまりWindows APIの関数を持つDLLファイルらは環境変数として設定されているパスの中にあるため、わざわざフルパスを指定しなくても呼び出すことが可能になっているという訳です。(前項でいうところのDLLファイルの探索範囲が既に登録されているということ)
よって動的とはいえませんが、自作DLLファイルを格納しておくフォルダのパスを環境変数の「Path」として登録するか、すでに環境変数の「Path」が設定されているフォルダに自作DLLファイルを保存することでも、ファイル名のみで関数を呼び出すこと自体は可能になります。
ただし基本的にSystem32のようなすでに設定されているディレクトリはシステムの根源部分であるため変更するのはご法度です。DLLファイルを動的に呼び出したい場合はこれまでに紹介したいずれかの方法で呼び出すことを推奨します。
まとめ
今回は自作したDLLファイルを動的に呼び出す方法を解説しました。
内容をまとめると下記の通りです。
DLLファイルの探索範囲はAddDllDirectory関数(WinAPI)で一時的に追加することが可能
LoadLibrary関数(WinAPI)を使うことでDLLファイルを直接読み込むことも可能
前回までのようにフルパスで呼び出し元のDLLファイルを設定しているとローカル環境に持って行ったり、少しでもフォルダ構成が変更されると呼び出しができなくなってしまいます。しかし、「呼び出す側のマクロファイル(.xlsm)と同階層」のような動的にDLLファイルのパスを設定することで、ローカルに持っていこうがフォルダ構成が変わろうが呼び出しができなくなることはありません。
マクロを誰かに渡す際もマクロファイルとDLLファイルだけが入ったフォルダをそのままわたして、「このフォルダの中身はいじらないでね。保存場所はどこでもいいよ。」というだけで済むため、自分の管轄外の場所(他部署や社外など)にも簡単に共有ができるようになります。
次回はこれまでの内容をまとめた応用編、OpenCVを使って入力された画像の顔認識を行うマクロ(及びDLLファイル)を作成していきます。
【次回】OpenCVを使った顔認識マクロを作成する
【前回】DLL関数を作成する(戻り値あり)
目次へ戻る