I have encountered some mystery with pidgenx.dll not being unloaded after checking a product key, pidgenx.dll is locked and cannot be deleted when closing the application (cleanup) First approach was to cleanup during FormClosing, as this didn't work i tried to add an ApplicationExit EventHandler to do the cleanup, still the dll isn't deleted and i am presented a access denied exception It seems like the dll is locked as long as the application is running, once it has closed completely the dll can be deleted without issues. programs.cs Code: ... Application.ApplicationExit += new EventHandler(Application_ApplicationExit); static void Application_ApplicationExit(object sender, EventArgs e) { try { string[] files = GetFiles(appPath, "*.dll|*.xrm-ms", SearchOption.TopDirectoryOnly); foreach (string file in files) { try { File.SetAttributes(file, FileAttributes.Normal); File.Delete(file); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } } public static string[] GetFiles(string path, string searchPattern, SearchOption searchOption) { string[] searchPatterns = searchPattern.Split('|'); List<string> files = new List<string>(); foreach (string sp in searchPatterns) files.AddRange(System.IO.Directory.GetFiles(path, sp, searchOption)); files.Sort(); return files.ToArray(); } Anyone know how to force the dll to be unloaded so the program can cleanup when closing?
Do you've tried to load the dll by Kernel32 API and releases using FreeLibrary() method? Code: [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll", SetLastError=true)] static extern bool FreeLibrary(IntPtr hModule); Usage: Code: FreeLibrary(LoadLibrary("My Dll Location Here"));
If not works, this method can help you: PS: Place in Application.Exit event: Code: int PID = Process.GetCurrentProcess().Id; string MyTempDir = @"""" + "Your temp dir of dlls here" + @""""; Process p = new Process(); p.StartInfo.FileName = "cmd.exe"; p.StartInfo.Arguments = "/C TASKKILL /F /PID " + PID + @" & RD /S /Q " + MyTempDir; p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; p.Start(); It kills your app process and delete the temporary directory with your dlls every closes if placed in exit event...
what Josh posted above looks pretty much what code we have been toying with the exception he is using TASKILL. let us know if it works better. here in VBnet: Code: Dim pro As Process pro = Process.GetCurrentProcess Dim appPath As String = String.Empty Dim filedata As New FileInfo(pro.MainModule.FileName) appPath = filedata.DirectoryName If My.Computer.FileSystem.FileExists(appPath & "\YOUR.dll") = True Then Try IO.File.SetAttributes(appPath & "\YOUR.dll", FileAttributes.Normal) IO.File.Delete(appPath & "\YOUR.dll") Catch ex As Exception Dim cmd As New Process With cmd .StartInfo.FileName = "cmd" .StartInfo.Arguments = " /c del /q" & appPath & "\YOUR.dll" .StartInfo.UseShellExecute = True .StartInfo.CreateNoWindow = True .StartInfo.WindowStyle = ProcessWindowStyle.Hidden .Start() End With pro.Kill() End Try End If
@user_hidden Theoretically your code is equivalent to "File.Delete()" method... I've just called the cmd.exe to kill the process before delete. In form-close event the process is running and the dll is locked. Maybe he will get a Non-Authorized exception when close the form using your code...
code seems to work, i still had a permission issue when testing but this seems to be caused by VS2010 IDE interfering (when ran from binary it works OK) @josh, indeed when i used user_hidden's code the authorization exception was still raised
FreeLibrary always worked for me as long as it was loaded with DLLimport .. Another for exit: Code: private void ClearTemp(string Folder, string process) { DirectoryInfo dir = new DirectoryInfo(Folder); foreach (FileInfo fi in dir.GetFiles()) { fi.IsReadOnly = false; fi.Delete(); } foreach (DirectoryInfo di in dir.GetDirectories()) { ClearFolder(di.FullName); di.Delete(); } Process[] name = Process.GetProcessesByName(process); foreach (Process n in name) { n.Kill(); } } private void ClearFolder(string FolderName) { DirectoryInfo dir = new DirectoryInfo(FolderName); foreach (FileInfo fi in dir.GetFiles()) { fi.IsReadOnly = false; fi.Delete(); } foreach (DirectoryInfo di in dir.GetDirectories()) { ClearFolder(di.FullName); di.Delete(); } } void ClearFolder(DirectoryInfo folder) { foreach (FileInfo file in folder.GetFiles()) { try { file.Delete(); } catch { } } foreach (DirectoryInfo subfolder in folder.GetDirectories()) { try { ClearFolder(subfolder); subfolder.Delete(); } catch { } } } Usage: Code: ClearTemp(My Directory, My Process); Not tested, just a quick think ? had a few so sorry for my bad!!!
Killing your process each and every time sounds to me like bad programming practice. A process should be allowed to exit cleanly and clean up after itself, always. I guess it doesn't really matter in reality (.NET probably cleans up after you), but it doesn't look good. LoadLibrary() and FreeLibrary() is the cleanest solution, if it works for you.
i think i now have a successfull working solution without killing processes, eg: FYI, just calling FreeLibrary(dll Handle) didn't work and sometimes resulted in still returning an access exception and/or some corrupted memory error Code: private void Form1_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = backgroundWorker1.IsBusy; while (FreeLibrary(GetModuleHandle(dllPath))) { //Must be run thrice in order to dereference the pointer } } [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr LoadLibrary(string libname); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr GetModuleHandle(string libname); [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] static extern bool FreeLibrary(IntPtr hModule); //To load the dll from dynamic path static string dllPath = appPath + "\\ProductKeyUtilities.dll"; IntPtr dllHandle = LoadLibrary(dllPath); [DllImport("ProductKeyUtilities.dll", EntryPoint = "PidGenX", CharSet = CharSet.Auto, SetLastError = true)] static extern int PidGenX(string ProductKey, string PkeyPath, string MSPID, string OEMID, IntPtr ProductID, IntPtr DigitalProductID, IntPtr DigitalProductID4); private string CheckProductKey(string ProductKey) { try { ... } }
Note that I have little experience calling native code from managed code, but I wonder. You have declared the PidGenX function using the standard DllImport way, but if this was C, you would use GetProcAddress with the dll handle. Maybe the runtime loads the DLL separately from your code, since your import of PidGenX does not reference the dllHandle. Maybe you can get this to work like intended if you use the GetProcAddress function and the Marshal.GetDelegateForFunctionPointer method as explained here?
My knowledge about calling native code is limited as well, but would like to use it as intended Is the page you linked to similar as to what is described here: http://blogs.msdn.com/b/jonathanswi...nmanaged-dll-from-.net-_2800_c_23002900_.aspx Basically what you are saying is to not use: Code: [DllImport("ProductKeyUtilities.dll", EntryPoint = "PidGenX", CharSet = CharSet.Auto, SetLastError = true)] static extern int PidGenX(string ProductKey, string PkeyPath, string MSPID, string OEMID, IntPtr ProductID, IntPtr DigitalProductID, IntPtr DigitalProductID4); and use it like: Code: IntPtr pAddressOfFunctionToCall = GetProcAddress(dllHandle, "PidGenX"); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int PidGenX(string ProductKey, string PkeyPath, string MSPID, string OEMID, IntPtr ProductID, IntPtr DigitalProductID, IntPtr DigitalProductID4); No idea how to use Marshal.GetDelegateForFunctionPointer as of yet, continuing reading...
Strange it works actually... i can make it work like so: (no need to call Loadlibrary as it isn't used like you already noticed) Code: private void Form1_FormClosing(object sender, FormClosingEventArgs e) { e.Cancel = backgroundWorker1.IsBusy; while (FreeLibrary(GetModuleHandle(dllPath))) { //Must be run thrice in order to dereference the pointer } } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr GetModuleHandle(string libname); [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] static extern bool FreeLibrary(IntPtr hModule); //To load the dll from dynamic path static string dllPath = appPath + "\\ProductKeyUtilities.dll"; [DllImport("ProductKeyUtilities.dll", EntryPoint = "PidGenX", CharSet = CharSet.Auto, SetLastError = true)] static extern int PidGenX(string ProductKey, string PkeyPath, string MSPID, string OEMID, IntPtr ProductID, IntPtr DigitalProductID, IntPtr DigitalProductID4);
Great, then I guess that should do it But if I'm understanding the article I linked correctly, then you should be able to do something like this also: Code: class Program { [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")] static extern int LoadLibrary( [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName); [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")] static extern IntPtr GetProcAddress( int hModule, [MarshalAs(UnmanagedType.LPStr)] string lpProcName); [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")] static extern bool FreeLibrary(int hModule); delegate int PidGenX( string ProductKey, string PkeyPath, string MSPID, string OEMID, IntPtr ProductID, IntPtr DigitalProductID, IntPtr DigitalProductID4 ); public bool Test() { int hModule = LoadLibrary(@"ProductKeyUtilities.dll"); if (0 == hModule) { return false; } IntPtr fpPidGenX = GetProcAddress(hModule, "PidGenX"); PidGenX dPidGenX = (PidGenX)Marshal.GetDelegateForFunctionPointer( fpPidGenX, typeof(PidGenX) ); dPidGenX(...); // This is where you call PidGenX() now FreeLibrary(hModule); return true/false; } } Perhaps you could add the LoadLibrary call to the class contructor (if that fits with how you've structured the program), and the call to FreeLibrary in the class destructor. Maybe a field set during construction for the dPidGenX variable.
lol, that sounds familiar @Calistoga, i couldn't make it work using your suggested code changes I send you a PM so you can access the source and have a look/go at it