To expand on this. You may want to use LoadLibrary
if the path to your dll is not known until runtime, or if you want to hold and release a dll perhaps you are in the middle of coding it. So you declare the dll like this without a full path to the file:
Public Declare PtrSafe Sub MyCoolFunction Lib "myLib.dll" () 'note not a full path to dll
Calling MyCoolFunction
in this state will raise an error File not found myLib.dll
So you add the code to LoadLibrary
hModule = LoadLibrary("C:\full\path\to\myLib.dll")
At this point the reference count of the dll for this process is one since only you loaded the dll. Now you can free the dll by calling FreeLibrary, which drops the ref count back to 0 and so the dll is unloaded (and can be deleted):
Debug.Assert FreeLibrary(hModule) = 1 'release was successful
Debug.Assert GetModuleHandle("myLib.dll") = 0 'module is not loaded in this process anymore
However what happens if you call MyCoolFunction
before you release it:
hModule = LoadLibrary("C:\full\path\to\myLib.dll")
MyCoolFunction 'call succeeds
Debug.Assert FreeLibrary(hModule) = 1
Debug.Assert GetModuleHandle("myLib.dll") = 0 'FAILS - module was not released
The assert fails and the file remains loaded. The reason is that once VBA knows where "myLib.dll" can be found, the first time MyCoolFunction
is called VBA increases the reference count on the DLL to make sure it is not released. Therefore even though we release the dll, VBA does not.
'myLib.dll refcount = 0
LoadLibrary '+1
'myLib.dll refcount = 1
MyCoolFunction '+1
'myLib.dll refcount = 2
FreeLibrary '-1
'myLib.dll refcount = 1
GetModuleHandle
So the solution is simple, we need to free the dll 2 times if the function is called, once for the manual LoadLibrary we invoked, once for the implicit LoadLibrary VBA does the first time MyCoolFunction is called.
Putting that all together we have:
Option Explicit
Public Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal libFilepath As String) As LongPtr
Public Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As LongPtr) As Long
Public Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal libFilepath As String) As LongPtr
Public Declare PtrSafe Sub MyCoolFunction Lib "myLib" ()
Sub TestDllUnloading()
On Error Resume Next
'myLib.dll refcount = 0
MyCoolFunction
Debug.Assert Err.Number = 53 'Error file not found: myLib
Err.Clear
Dim hModule As LongPtr
hModule = LoadLibrary("C:\full\path\to\myLib.dll")
Debug.Assert hModule <> 0 'loaded successfully
Debug.Assert GetModuleHandle("MyLib") = hModule
'myLib refcount = 1
MyCoolFunction
'myLib refcount = 2
Debug.Assert Err.Number = 0 'no Error calling the function
Debug.Assert FreeLibrary(hModule) = 1 'Freed successfully
'myLib refcount = 1
MyCoolFunction
Debug.Assert Err.Number = 0 'Again, no Error calling the function
Debug.Assert FreeLibrary(hModule) = 1 'Freed successfully a 2nd time
'myLib refcount = 0
MyCoolFunction
Debug.Assert Err.Number = 453 'Specified DLL function not found - because dll was unloaded
Debug.Assert GetModuleHandle("myLib") = 0
Kill "C:\full\path\to\myLib.dll" 'succeeds (Assuming not locked by some other process)
End Sub
Bear in mind that any memory the dll owns could be released at the point it is unloaded - e.g. objects you get from the dll function. So make sure these are set to nothing before releasing the dll.