gsapi_init_with_args is made: -100

5.5k Views Asked by At

I'm trying to build a PostScript to PDF Converter using Ghostscript.Net. The Args that GetArgs return, are the ones I usually use to call gswin32c.exe and they work fine.

But every time i call Process, i get an error Saying "An error occured when call to 'gsapi_init_with_args' is made: -100". Googling that error didn't bring anything up so I thought I might ask here.

Are there differnet arguments to consider when calling the .dll directly with Ghostscript.net? Or did I made a mistake somewhere else?

Here's my class:

public class PdfConverter
{
    #region Private Fields
    private List<GhostscriptVersionInfo> _Versions = GhostscriptVersionInfo.GetInstalledVersions(GhostscriptLicense.GPL | GhostscriptLicense.AFPL | GhostscriptLicense.Artifex);
    #endregion


    #region Private Properties
    private GhostscriptVersionInfo Version { get; set; }
    #endregion


    #region Construction
    public PdfConverter()
    {
        Version = GhostscriptVersionInfo.GetLastInstalledVersion(); 
    }
    #endregion


    #region Public Members
    public bool ConvertToPdf(DirectoryInfo dir)
    {
        var d = dir;
        if(!d.Exists)
            return false;

        var postScriptFiles = d.GetFiles("*.ps");
        var pdfFiles        = postScriptFiles.Select(psf => new FileInfo(Path.ChangeExtension(psf.FullName, ".pdf")));

        foreach(var file in postScriptFiles) {
            //ThreadPool.QueueUserWorkItem(new WaitCallback((o) => { 
                Process(file, new FileInfo(Path.ChangeExtension(file.FullName, ".pdf")));
            //}));
        }

        pdfFiles.ForEach(pdf => pdf?.Refresh());
        return pdfFiles.All(pdf => pdf.Exists);
    }
    #endregion


    #region Private Helpers
    private void Process(FileInfo inputFile, FileInfo outputFile)
    {
        Console.WriteLine($"Converting {inputFile} to {outputFile}");
        var proc = new GhostscriptProcessor(Version, true);
        proc.Process(GetArgs(inputFile, outputFile).ToArray(), new ConsoleStdIO(true, true, true));
    }


    private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
    {
        return new [] {
            $"-q ",
            $"-sDEVICE=pdfwrite",
            $"-dSAFER",
            $"-dNOPAUSE",
            $"-dBATCH",
            $"-sPAPERSIZE=a4",
            $"-dEmbedAllFonts=true",
            $"-dAutoRotatePages=/None",
            $"-sOutputFile=\"{outputFile.FullName}\"",
            $"-dCompatibilityLevel#1.4",
            $"-c .setpdfwrite",
            $"-f \"{inputFile.FullName}\""
        };
    }
    #endregion
}

Edit: I forgot to mention: To implement it i had to make my own GhostsdcriptStdIO class. I admit that I'm not entirely sure if I did this right. Although it does get instanciated without exceptions, and override StdOut(...) get's called, and the output is written to the console as expected. override void StdError(...) get's called as well. And also written to the console as expeted. The Output of the error btw is:

"**** Could not open the file "c:\temp\test.pdf"" "**** Unable to open the initial device, quitting."

Here's my ConsoleStdIO class:

public class ConsoleStdIO : Ghostscript.NET.GhostscriptStdIO
{
    #region Construction
    public ConsoleStdIO(bool handleStdIn, bool handleStdOut, bool handleStdError) : base(handleStdIn, handleStdOut, handleStdError) { }  
    #endregion


    #region Overrides
    public override void StdError(string error)
    {
        var foo    = Encoding.Default.GetBytes(error);
        var lenght = foo.Length;

        using (var err = Console.OpenStandardError()) {
            if(err.CanWrite)
                err.Write(foo, 0, lenght);
        }
    }

    public override void StdIn(out string input, int count)
    {
        byte[] bytes = new byte[0];
        using(var stdInput = Console.OpenStandardInput()) {
            stdInput.Read(bytes, 0, count);
        }
        input = Encoding.Default.GetString(bytes);
    }

    public override void StdOut(string output)
    {
        var foo    = Encoding.Default.GetBytes(output);
        var lenght = foo.Length;

        using (var err = Console.OpenStandardError()) {
            if(err.CanWrite)
                err.Write(foo, 0, lenght);
        }           
    }
    #endregion
}

Again: doing the same operation with the exact same files and arguments using gswin32c.exe works fine. Happy Hacking

2

There are 2 best solutions below

0
On BEST ANSWER

So I solved the problem...
After taking KenS' advice I could run the application without Ghostscript (not Ghostscript.NET) giving me any errors. But it did not produce an actual PDF File.

So KenS's answer did not quite solve the problem, but since 'less is more' and since he took the time to talk to me on IRC to verify that my args in itself were correct, I'll give the answer points nonetheless.

What actually solved my was the following:
Here my original GetArgs(...)

    private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
    {
        return new [] {
            $"-sDEVICE=pdfwrite",
            $"-dNOPAUSE",
            $"-dBATCH",
            $"-sPAPERSIZE=a4",
            @"-sFONTPATH=" + System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts),
            $"-sOutputFile={outputFile.FullName}",
            $"{inputFile.FullName}",
        };
    }

Someone in #csharp pointed out to me, that in c, the first argument is always the name of the command. So he suggested to just put "gs" as the first argument (as a dummy) and try... And that's what actually solved my problem.

So this is how the working GetArgs(...) looks:

    private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
    {
        return new [] {
            $"gs",
            $"-sDEVICE=pdfwrite",
            $"-dNOPAUSE",
            $"-dBATCH",
            $"-sPAPERSIZE=a4",
            @"-sFONTPATH=" + System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts),
            $"-sOutputFile={outputFile.FullName}",
            $"{inputFile.FullName}",
        };
    }
3
On

Error -100 is gs_error_Fatal, which means 'something catastrophic went wrong'. Its an indication that the program failed to start up properly and we can't tell why. The back channel may contain more information.

And indeed, the back channel tells you what's wrong:

**** Could not open the file "c:\temp\test.pdf **** Unable to open the initial device, quitting.

Ghostscript is unable to open the output file, which means it can't open the pdfwrite device (because that requires an output file) so it aborts the operation.

There could be a number of reasons why Ghostscript can't open the output file. The first thing I'd do is trim down the number of arguments;

You don't want -q (quiet) when you are trying to debug a problem, you want all the information you can get.

I'd remove -dSAFER at least to start with, because that prevents Ghostscript accessing directories outside the current working directory and certain 'special' ones. It may well prevent you accessing the temp directory.

You don't need to set EmbedAllFonts when its the same value as the default.

You could drop the CompatibilityLevel (and note that you've used a # there instead of an =) switch, and the AutoRotatePages while getting this to work.

The "-c .setpdfwrite -f" string has been pointless for years but people still keep using it. All that does these days is slow down the start of processing, ditch it.

Finally you can try changing the backslash ('\') characters to forward slash ('/') in case your string handling is messing that up, or use double backslashes (I'd use the forward slash myself).

You should also check that c:\test\temp.pdf doesn't exist, or if it does exist is not read-only or already open in a different application.