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

第2回、第3回、第4回を通して関数の基本となる引数と戻り値の設定方法を解説してきたため、C++の知識さえあればVBAと連携可能な関数はある程度何でも作成できるような状態となりました。
しかし、これまでのコードはVBAで自作DLL関数を呼び出すために、呼び出し元であるDLLファイルの”フルパス”を毎回丁寧に書いてあげる必要がありました。正直なお話、DLLファイルの場所を移動しただけで使えなくなってしまうのは面倒ですし、もう少し融通の利くような設定にしておきたいです。
そこで、今回は自作したDLLファイルを動的に呼び出す方法を解説していきます。これにより”Excelファイル(.xlsm)の存在するディレクトリ”というような動的な場所を設定することが可能になります。
DLLファイルを動的に呼び出す
DLLファイルを呼び出すにはWindowsAPIの「AddDllDirectory関数」と、AddDllDirectory関数を使うために同じくWindowsAPIの「SetDefaultDllDirectories関数」が必要になります。
DLLファイルは呼び出す際に、環境変数のPathとして設定されているディレクトリをすべて探索して該当のファイルを取得するというような処理が行われます。AddDllDirectory関数はこのDLLファイルの探索範囲に、指定したパスを一時的に追加することができる関数です。そしてSetDefaultDllDirectories関数はこのAddDllDirectory関数の機能を有効にするための関数です。
SetDefaultDllDirectories関数(libloaderapi.h)|Microsoft
AddDllDirectory関数(libloaderapi.h)|Microsoft
これまで自作DLLファイルの所在はフルパスで表していましたが、このAddDllDirectory関数で自作DLLファイルのディレクトリのパスを探索範囲に追加すれば、わざわざフルパスを書かずともWindowsAPIのようにDLLファイルの名前だけで呼び出しが可能になります。
2つの関数を使うには下記の構文を書いておく必要があります。
‘【SetDefaultDllDirectories関数】
Private Declare PtrSafe Function SetDefaultDllDirectories Lib “kernel32.dll” _
(ByVal DirectoryFlags As Long) As Long
‘【AddDllDirectory関数】
Private Declare PtrSafe Function AddDllDirectory Lib “kernel32.dll” _
(ByVal fileName As String) As LongPtr
上記の構文も含め、自作DLLファイルの関数定義までをまとめると下記のようなコードになります。
これまでフルパスで書いていた自作DLLファイルの関数定義のコードがファイル名だけになったことがわかります。しかし、このままではまだ呼び出すことができないので、メイン処理部でDLLファイルの探索範囲を追加し自作DLL「VBA_DLL.dll」の呼び出しを有効にしていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
'動的に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ファイルの関数定義(DLLはファイル名だけでOK) 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() 'メイン処理 End Sub |
また、定数の「LOAD_LIBRARY_SEARCH_DEFAULT_DIRS」はSetDefaultDllDirectories関数に渡すための引数となる数値です。この値を渡すことでAddDllDirectory関数によるDLLファイルの探索範囲を追加する機能が使用可能となります。(決まり文句なので”おまじない”と思ってください)
また動的とはいえませんが、自作DLLファイルを格納しておくフォルダのパスを環境変数の「Path」として登録するか、すでに環境変数の「Path」が設定されているフォルダに自作DLLファイルを保存することでも、ファイル名のみで関数を呼び出すことが可能になります。こちらに関しては「自作DLLファイルを”動的”に呼び出す」とは少し違う内容となるため参考程度に読んでください。
Tips DLLファイルと環境変数 Path
「Windows APIを使う」ということは「Windowsが標準装備として持っているDLLファイル内にある関数を呼び出す」ということを意味しています。このときVBAで自作DLLファイルを呼び出すときとは違いWindows APIの関数の呼び出しではDLLのフルパスを書く必要はありません。
たとえばSleep関数でいえば、下記のように書くことで関数の使用が可能になりますが、DLLファイルである「kernel32.dll」の所在となるフルパスはどこにも記載していません。
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As LongPtr)
これはWindows APIと呼ばれる関数を持つDLLファイルら(kernel32.dllなど)の存在しているディレクトリのパスが、Windowsの標準設定により”通された”状態になっているためです。このパスが通されたという状態は、「環境変数のPathとして設定された」と言い換えてもよいです。
環境変数とは簡単にいえば、どのプログラムからでもアクセスできるように設定されているコンピュータ自身が持っている情報です。VBAでDLLファイルを呼び出す際はこの環境変数のPathとして設定されているすべてのパスの中から、指定した名称のファイルが探索されます。
「kernel32.dll」は「system32」というフォルダに保存されており、この「system32」は環境変数として設定されています。つまりWindows APIの関数を持つDLLファイルらは環境変数として設定されているパスの中にあるため、わざわざフルパスを指定しなくても呼び出すことが可能になっているという訳です。
SetDefaultDllDirectories関数の構文
この関数はDLLファイルを呼び出すときに探索するディレクトリの範囲を変更することができます。
SetDefaultDllDirectories関数の構文は以下のように書きます。
res = 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以外の整数値」がLong型で返ってきます。
AddDllDirectory関数の構文
この関数はDLLファイルを呼び出すときに探索するディレクトリを追加することができます。
AddDllDirectory関数の構文は以下のように書きます。
res = AddDllDirectory(fileName)
引数のfileNameには自作DLLファイルを置いておくディレクトリのパスを入力します。
ここで注意しないといけないのは引数として渡す文字列をUnicodeに変換する必要があるという点です。これはVBAの「StrConv関数」で簡単に変換することが出来るため、正確には下記のようにUnicodeに変換した文字列を引数とする必要があります。
res = AddDllDirectory(StrConv(FolderPath, vbUnicode))
このとき、FolderPathには自作DLLファイルを置いておくディレクトリのパスを入力しますが、このパスは「ThisWorkbook.Path」のような、いわゆる動的なパスであっても問題ありません。
戻り値としては関数が失敗したら「0」、成功したら「0以外の整数値」がLong型で返ってきます。
自作DLLファイルの動的呼び出しサンプルコード
これまでの内容をまとめてVBAマクロとして使用する場合は以下のように書きます。下記のコードでは前回作成した「GetFileSize1関数」を呼び出して選択したファイルのサイズを調べることができます。
ただ前回とは違い動的な呼び出しを行っているため、Excelファイル(.xlsm)と同階層にある「VBA_DLL.dll」を呼び出すような処理となっており、WindowsAPIのようにファイル名だけで自作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 |
このサンプルコードでは「GetFileSize1関数」しか呼び出していませんが、「VBA_DLL.dll」の呼び出しは成功しているため、同ファイル内にある第2回、第3回で実装した関数もまとめて呼び出しが可能となっています。
まとめ
今回は自作したDLLファイルを動的に呼び出す方法を解説しました。
内容をまとめると下記の通りです。
DLLファイルの探索範囲はAddDllDirectory関数(WinAPI)で一時的に追加することが可能
AddDllDirectory関数を有効にするにはSetDefaultDllDirectories関数(WinAPI)が必要
今回出てきたAddDllDirectory関数を使ってDLLファイルの動的な呼び出しを可能にすることで、コードの共有が非常に簡単にできるようになります。
前回までのようにフルパスで呼び出し元のDLLファイルを設定しているとローカル環境に持って行ったり、少しでもフォルダ構成が変更されると呼び出しができなくなってしまいます。しかし、「呼び出す側のマクロファイル(.xlsm)と同階層」のような動的にDLLファイルのパスを設定することで、ローカルに持っていこうがフォルダ構成が変わろうが呼び出しができなくなることはありません。
マクロを誰かに渡す際もマクロファイルとDLLファイルだけが入ったフォルダをそのままわたして、「このフォルダの中身はいじらないでね。保存場所はどこでもいいよ。」というだけで済むため、自分の管轄外の場所(他部署や社外など)にも簡単に共有ができるようになります。
次回はこれまでの内容をまとめた応用編、OpenCVを使って入力された画像の顔認識を行うマクロ(及びDLLファイル)を作成していきます。
【次回】OpenCVを使った顔認識マクロを作成する
【前回】DLL関数を作成する(戻り値あり)
目次へ戻る