2

I need to delete a DLL file when I am done using it (cleanup after code is completed).

I try using "LoadLibrary" and "FreeLibrary" in Excel VBA but no matter what I do Excel.exe clings to the DLL file.

 Public Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
 Public Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long

 Private Sub Load_Unload_DLL()

     Dim lb As Long, pa As Long
     lb = LoadLibrary("C:\Users\Administrator\Documents\MathLibrary.dll")

     'MsgBox "Library address: " & lb

     Dim FreeResult As Long
     FreeResult = 1

     Do Until FreeResult = 0
         FreeResult = FreeLibrary(lb)
     Loop

     Name "C:\Users\Administrator\Documents\MathLibrary.dll" As "C:\Users\Administrator\Documents\MathLibrary2.dll"

     Kill ("C:\Users\Administrator\Documents\MathLibrary2.dll")

 End Sub

Despite "FreeResult" equaling "0" I receive the following error when executing the "Kill" command:

Path Access Error

And Process Explorer showing that the DLL file is indeed still loaded by Excel:

Process Explorer

The file can be renamed, but not deleted (as indicated by the code).

Am I missing something?

Shrout1
  • 2,497
  • 4
  • 42
  • 65
  • FreeLib merely decrements the reference count. Check for the FreeLib return value. If its not zero then you haven't actually unloaded it. – cyboashu Aug 16 '17 at 20:02
  • @cyboashu Thanks! I had already tested that and I updated my code to address this. Even when FreeLib returns 0 I still encounter the error. – Shrout1 Aug 16 '17 at 20:17
  • I don't recommend loops with `FreeLibrary`. I've tried do it with and without `GetModuleHandle` and it is kind of unstable. More details [here](https://stackoverflow.com/questions/47575675/excel-vba-with-c-dll-sometimes-crashes) – ingwarus Apr 03 '18 at 12:09

2 Answers2

1

I've just rediscovered this answer and with the benefit of some more experience suggest it NOT be used.

See: https://stackoverflow.com/a/28745693/3451115

One of the issues that I faced with this approach is that, although the library was freed, trying to reload it crashed the host (MS Word for me).

Evil as it is, it may meet a need so be advised and use with caution.


Depending on what the root cause of your problem is this may or may not help, but I think it is a step in the right direction.

The return value of 0 from FreeLib indicates that there has been an error and NOT that the library has been freed, see here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms683152%28v=vs.85%29.aspx

From what I understand FreeLib should only be called as many times as LoadLibrary was used... so, rather than looping until an error appears (FreeLib = 0) you could instead have a loop that frees the library and then checks if the library is still loaded, try something like this:

    Do Until lb = 0
        FreeLibrary lb
        If CBool(Err.LastDllError) Then
            debug.print "dll error" & " " & Err.LastDllError
            Err.Clear
            Exit Do
        End If

        lb = 0 ' Reset lb needed for test on next line

        ' Check if the dll really has been released...
        lb = GetModuleHandle("C:\Users\Administrator\Documents\MathLibrary.dll")
    Loop

You'll need to declare this function to use GetModuleHandle (VBA7 version): Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal GetModuleHandle As String) As LongPtr.

Also, I'm declaring LoadLib and FreeLib with LongPtr for VBA7 like this:

Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr
Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As LongPtr) As Long

Hope it helps :)

SlowLearner
  • 3,086
  • 24
  • 54
0

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.

Greedo
  • 4,967
  • 2
  • 30
  • 78