This project has moved. For the latest updates, please go here.

ImageMagick Doesn't Release Ghostscript dll In A Timely Way

Apr 4 at 6:58 PM
Edited Apr 4 at 7:13 PM
Hello;
I am using Magick.Net to convert PDF files and export them to Tiff format. I have a series of unit tests to test this code. One test attempts to delete the Ghostscript dll (to force an exception condition and test error/exception handling), and other tests are intended to verify that the code successfully converts and exports to TIFF. I have a master test-driver method to ensure that the delete-and-handle-exception test runs first in the test sequence. Even so, when the sequence is run multiple times in close succession, the exception-handling test fails (with an access-violation exception) when it attempts to delete the Ghostscript dll. The only thing I have been able to conclude is that Magick.Net does not release the Ghostscript dll in a prompt and predictable manner and that the Ghostscript dll (gsdll32.dll – using 32 bit for backwards compatibility) still seems to be held by one of the “successful” unit tests at the end of the prior execution of the test sequence. This problem doesn’t seem to occur if the tests are run in debug mode (with no breakpoints), but is a problem if the test-series is run from Test Explorer and might be a problem if run from an integration server.

I have attached code snippets – is there anything I can do differently, or add, to ensure the release of the Ghostscript dll to avoid the issue above? Or is there an issue with Magick.Net's handling of the the Ghostscript dll?

Thanks for any assistance or insight!
// unit-test class
public class UT_FormatConversion
    {
        private IFormatConversion fFormatConversion;
        private IPlgRegELib fPlgRegELib;
        private string fINIFile = @"C:\DevVS_CSharp\RegELib\Test_ConvertToTiff2\RegE.INI";
        private int fTestCaseID;

        [TestInitialize]
        public void TestInitialize()
        {
            fPlgRegELib = new TPlgRegELib();
            ConnectToDB();
        }


        [TestMethod]
        public void TestDriver()
        {
            ImgConvPdfToTiffFail();
            ImgConvPdfToTiffSuccess();
            ImgConvPdfToTiffPlugin();
        }

        /// <summary>
        /// Tests exception/error handling under various scenarios, including missing Ghostscript components
        /// </summary>
        public void ImgConvPdfToTiffFail()
        {
            string sourceDirectory = @"C:\DevVS_CSharp\RegELib\Test_ConvertToTiff2";
            string currDir = Directory.GetCurrentDirectory();
            string ghostScriptDLL = "gsdll32.dll";
            string ghostScriptDLLsv = "gsdll32_sv.dll";
            string ghostScriptExe = "gswin32c.exe";
            string ghostScriptEXEsv = "gswin32c_sv.exe";

            // Verifies the backup-up GhostScript files exist - if not found, manually copy the files into place
            ghostScriptDLLsv = sourceDirectory + @"\" + ghostScriptDLLsv;
            Assert.IsTrue(File.Exists(ghostScriptDLLsv));
            ghostScriptEXEsv = sourceDirectory + @"\" + ghostScriptEXEsv;
            Assert.IsTrue(File.Exists(ghostScriptEXEsv));

            ghostScriptDLL = currDir + @"\" + ghostScriptDLL;
            ghostScriptExe = currDir + @"\" + ghostScriptExe;

            // set up the other files
            string sourcePdfFileRoot = "NonexistentFile";
            string sourcePdfFileExt = "pdf";

            string fullSourcePDFFile = sourceDirectory + @"\" + sourcePdfFileRoot + "." + sourcePdfFileExt;
            Assert.IsFalse(File.Exists(fullSourcePDFFile));

            string destPath = @"C:\DevVS_CSharp\RegELib\Test_ConvertToTiff2";
            string destinationExtension = "tiff";
            string destPathFile = destPath + @"\" + sourcePdfFileRoot + "." + destinationExtension;

            if (File.Exists(destPathFile))
            { File.Delete(destPathFile); }
            Assert.IsFalse(File.Exists(destPathFile));

            // Test for failure due to missing GhostScript files
            // Test for missing GhostScript DLL
            if (File.Exists(ghostScriptDLL))
                File.Delete(ghostScriptDLL);   // <<--THIS IS THE STATEMENT THAT FAILS BECAUSE THE DLL HAS NOT RELEASED FROM A PREVIOUS TEST CYCLE
            Assert.IsFalse(File.Exists(ghostScriptDLL));

            TFormatConversion tiffConverter = new TFormatConversion();
            Assert.IsNotNull(tiffConverter);

            try
            {
                Assert.IsFalse(tiffConverter.ConvertToTiff(fullSourcePDFFile, destPath));
                Assert.AreNotEqual(tiffConverter.ErrorMsg, null);
                string expectedErrorMsg = "File " + ghostScriptDLL + " could not be found - aborting";
                Assert.AreEqual(tiffConverter.ErrorMsg, expectedErrorMsg);
            }
            catch (Exception e) { throw; }
            finally
            {
                // Ref-establish the GS dll so it's present for later tests
                File.Copy(ghostScriptDLLsv, ghostScriptDLL);
                File.SetAttributes(ghostScriptDLL, FileAttributes.Normal);
                Assert.IsTrue(File.Exists(ghostScriptDLL));
            }
... (more code
        } // end ImgConvPdfToTiffFail
}  // end test class

//Format-conversion class
 public class TFormatConversion : IFormatConversion
    {
        #region PrivateVariables
        private string fErrorMsg;
        #endregion

        
        #region Properties
        public string ErrorMsg
        {
            get { return fErrorMsg; }
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Converts an image of any supported format to TIFF format
        /// </summary>
        /// <param name="aSourcePathFile">The full path and file name of the file to be read and converted</param>
        /// <param name="aDestinationPathFile">The full destination path and file name for the converted file</param>
        /// <returns>boolean - true for successful conversion, false if unsuccessful, ErrorMg contains error-detail in case return is false</returns>
        public bool ConvertToTiff(string aSourcePathFile, string aDestinationPathFile)
        {
            string ghostScriptDLL = "gsdll32.dll";
            string ghostScriptExe = "gswin32c.exe";

            // if dealing with PDFs, MagickNet is very sensitive to the GhostScript directory. If that setting is in error,
            // MagickNet may fail to find the file to be read and converted or throw other exceptions

            string currDirectory = Directory.GetCurrentDirectory();
            ghostScriptDLL = currDirectory + @"\" + ghostScriptDLL;
            if (!File.Exists(ghostScriptDLL))
            {
                fErrorMsg = "File " + ghostScriptDLL + " could not be found - aborting";
                return false;
            }
            ghostScriptExe = currDirectory + @"\" + ghostScriptExe;
            if (!File.Exists(ghostScriptExe))
            {
                fErrorMsg = "File " + ghostScriptExe + " could not be found - aborting";
                return false;
            }

            // Validate the destination path/file
            if ((aDestinationPathFile.Substring(aDestinationPathFile.Length - 4).ToLower() != ".tif") &&
                (aDestinationPathFile.Substring(aDestinationPathFile.Length - 5).ToLower() != ".tiff"))
            {
                fErrorMsg = "Destination filename must have an extension of .tif or .tiff";
                return false;
            }

            MagickNET.SetGhostscriptDirectory(currDirectory);

            // A combination of settings at different levels was required to get the desired results
            // High-level settings here - image-level settings later
            MagickReadSettings settings = new MagickReadSettings();
            settings.Density = new Density(192, 192);
            settings.BackgroundColor = new MagickColor(MagickColors.White);
            settings.CompressionMethod = CompressionMethod.Group4;            

            string sourceFileRoot = Path.GetFileNameWithoutExtension(aSourcePathFile);

            if (File.Exists(aDestinationPathFile))
            { File.Delete(aDestinationPathFile); }

            try
            {
                using (MagickImageCollection images = new MagickImageCollection())
                {
                    images.Read(aSourcePathFile, settings);

                    foreach(MagickImage image in images)
                    {
                        // Image-level settings - Quality affects file size and viewability, Alpha affects form
                        // background effects
                        image.Quality = 60;
                        image.Alpha(AlphaOption.Remove);
                        image.BitDepth(1);
                        image.CompressionMethod = CompressionMethod.Group4;
                    }
                    images.Write(aDestinationPathFile);
                }
            }
            catch (Exception e)
            {
                fErrorMsg = e.Message;
                return false;
            }

            if (!File.Exists(aDestinationPathFile))
            {
                fErrorMsg = "The file " + aDestinationPathFile + " could not be found after the conversion attempt!";
                return false;
            }

            return true;
        }
}
Coordinator
Apr 4 at 10:01 PM
When Ghostscript code is executed it will load the dll into memory. This dll won't be unloaded until your process exits. I am guessing that you are keeping the test execution running. You can turn it of here: Test -> Test Settings -> Keep Test Execution Engine Running. I also need to do this with my Magick.NET tests because I disable something that cannot be restored.

p.s. What does the f stand for in your prifate members?
Apr 7 at 3:48 PM
The "f" indicates a class-level variable - it's a coding standard where I work, rooted in the dark ages of their Delphi past