[C#] DLLimport (unload)

Discussion in 'Mixed Languages' started by FreeStyler, Aug 21, 2012.

  1. FreeStyler

    FreeStyler MDL Guru

    Jun 23, 2007
    3,557
    3,832
    120
    #1 FreeStyler, Aug 21, 2012
    Last edited by a moderator: Apr 20, 2017
    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?
     
  2. Josh Cell

    Josh Cell MDL Developer

    Jan 8, 2011
    3,515
    7,170
    120
    #2 Josh Cell, Aug 21, 2012
    Last edited by a moderator: Apr 20, 2017
    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"));
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  3. CODYQX4

    CODYQX4 MDL Developer

    Sep 4, 2009
    4,813
    45,775
    150
    #3 CODYQX4, Aug 21, 2012
    Last edited: Apr 12, 2019
    .
     
  4. Josh Cell

    Josh Cell MDL Developer

    Jan 8, 2011
    3,515
    7,170
    120
    #4 Josh Cell, Aug 21, 2012
    Last edited by a moderator: Apr 20, 2017
    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...
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  5. FreeStyler

    FreeStyler MDL Guru

    Jun 23, 2007
    3,557
    3,832
    120
    Gonna test that when i get home, i'll get back with results later
     
  6. user_hidden

    user_hidden MDL Expert

    Dec 18, 2007
    1,034
    1,061
    60
    #6 user_hidden, Aug 21, 2012
    Last edited by a moderator: Apr 20, 2017
    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
    
     
  7. Josh Cell

    Josh Cell MDL Developer

    Jan 8, 2011
    3,515
    7,170
    120
    @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...
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  8. FreeStyler

    FreeStyler MDL Guru

    Jun 23, 2007
    3,557
    3,832
    120
    #8 FreeStyler, Aug 21, 2012
    Last edited: Aug 21, 2012
    (OP)
    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
     
  9. Alphawaves

    Alphawaves Super Moderator/Developer
    Staff Member

    Aug 11, 2008
    6,218
    22,277
    210
    #9 Alphawaves, Aug 22, 2012
    Last edited by a moderator: Apr 20, 2017
    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 ?

    :laie: had a few so sorry for my bad!!!
     
  10. Calistoga

    Calistoga MDL Senior Member

    Jul 25, 2009
    421
    199
    10
    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.
     
  11. FreeStyler

    FreeStyler MDL Guru

    Jun 23, 2007
    3,557
    3,832
    120
    #11 FreeStyler, Aug 22, 2012
    Last edited by a moderator: Apr 20, 2017
    (OP)
    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
    {
    ...
    }
    }
    
     
  12. Calistoga

    Calistoga MDL Senior Member

    Jul 25, 2009
    421
    199
    10
    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?
     
  13. FreeStyler

    FreeStyler MDL Guru

    Jun 23, 2007
    3,557
    3,832
    120
    #13 FreeStyler, Aug 22, 2012
    Last edited by a moderator: Apr 20, 2017
    (OP)
    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...
     
  14. FreeStyler

    FreeStyler MDL Guru

    Jun 23, 2007
    3,557
    3,832
    120
    #14 FreeStyler, Aug 22, 2012
    Last edited by a moderator: Apr 20, 2017
    (OP)
    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);
     
  15. Calistoga

    Calistoga MDL Senior Member

    Jul 25, 2009
    421
    199
    10
    #15 Calistoga, Aug 22, 2012
    Last edited by a moderator: Apr 20, 2017
    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.
     
  16. CODYQX4

    CODYQX4 MDL Developer

    Sep 4, 2009
    4,813
    45,775
    150
    #16 CODYQX4, Aug 22, 2012
    Last edited: Apr 12, 2019
    .
     
  17. Josh Cell

    Josh Cell MDL Developer

    Jan 8, 2011
    3,515
    7,170
    120
    Calling cmd.exe never will be good.

    But for emergency purposes I'd like to call it.
     
    Stop hovering to collapse... Click to collapse... Hover to expand... Click to expand...
  18. Calistoga

    Calistoga MDL Senior Member

    Jul 25, 2009
    421
    199
    10
    That's your right. In most cases however, I believe there is a better way of doing it :)
     
  19. FreeStyler

    FreeStyler MDL Guru

    Jun 23, 2007
    3,557
    3,832
    120
    #19 FreeStyler, Aug 22, 2012
    Last edited: Aug 22, 2012
    (OP)
    lol, that sounds familiar :D

    @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 ;)
     
  20. CODYQX4

    CODYQX4 MDL Developer

    Sep 4, 2009
    4,813
    45,775
    150
    #20 CODYQX4, Aug 29, 2012
    Last edited: Apr 12, 2019
    .