I'm extending my Inno-Setup script with code that I can best implement in C# in a managed DLL. I already know how to export methods from a managed DLL as functions for use in an unmanaged process. It can be done by IL weaving, and there are tools to automate this:
So after exporting, I can call my functions from Pascal script in an Inno-Setup installer. But then there's one issue: The DLL can't seem to be unloaded anymore. Using Inno-Setup's UnloadDLL(...)
has no effect and the file remains locked until the installer exits. Because of this, the setup waits for 2 seconds and then fails to delete my DLL file from the temp directory (or install directory). In fact, it really stays there until somebody cleans up the drive.
I know that managed assemblies cannot be unloaded from an AppDomain anymore, unless the entire AppDomain is shut down (the process exits). But what does it mean to the unmanaged host process?
Is there a better way to allow Inno-Setup to unload or delete my DLL file after loading and using it?
Short answer: No, it is impossible. Win32 doesn't provide an API to unload a DLL of another process. If a library is freed unexpectedly, the process will crash. This leads to a serious security hole as it breaks process protection mechanism.
To determine whether a DLL (or EXE) is managed or unmanaged, use dumpbin.exe with the /dependents switch. If you see mscoree. dll in the output, then the assembly is a managed assembly.
As suggested in other answers, you can launch a separate process at the end of the installation that will take care of the cleanup, after the installation processes finishes.
A simple solution is creating an ad-hoc batch file that loops until the DLL file can be deleted and then also deletes the (now empty) temporary folder and itself.
procedure DeinitializeSetup(); var FilePath: string; BatchPath: string; S: TArrayOfString; ResultCode: Integer; begin FilePath := ExpandConstant('{tmp}\MyAssembly.dll'); if not FileExists(FilePath) then begin Log(Format('File %s does not exist', [FilePath])); end else begin BatchPath := ExpandConstant('{%TEMP}\') + 'delete_' + ExtractFileName(ExpandConstant('{tmp}')) + '.bat'; SetArrayLength(S, 7); S[0] := ':loop'; S[1] := 'del "' + FilePath + '"'; S[2] := 'if not exist "' + FilePath + '" goto end'; S[3] := 'goto loop'; S[4] := ':end'; S[5] := 'rd "' + ExpandConstant('{tmp}') + '"'; S[6] := 'del "' + BatchPath + '"'; if not SaveStringsToFile(BatchPath, S, False) then begin Log(Format('Error creating batch file %s to delete %s', [BatchPath, FilePath])); end else if not Exec(BatchPath, '', '', SW_HIDE, ewNoWait, ResultCode) then begin Log(Format('Error executing batch file %s to delete %s', [BatchPath, FilePath])); end else begin Log(Format('Executed batch file %s to delete %s', [BatchPath, FilePath])); end; end; end;
You could add a batch script (in the form of running cmd -c) to be executed at the end of setup that waits for the file to be deletable and deletes it. (just make sure to set the inno option to not wait for the cmd process to complete)
You could also make your installed program detect and delete it on first execution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With