ATTENTION: WiBit.Net will be temporarily taken offline for routine maintenance on 9/22/2018. The site is expected to be down for 2-3 hours.
We apologize for any inconvenience.
WiBit.Net Blog (48)

 Integrating GhostScript with C#

Sat Nov 19, 2011 2268 views
kevin

Integrating GhostScript with C#

If you never used GhostScript for image manipulation then you are really missing out!

There are tons of amazing outlets that this software provides, it is amazing! The cool thing about GhostScript is that it can be used as an API and cleanly integrated into other programming languages..

Throughout my development career I have been tasked with image and document conversions a lot! When you’re writing Application Integration style software there are often times where one system outputs a specific file format (PDF for example) and the receiving system does not accept that file type and it must be converted before delivery (PostScript for example). I found that GhostScript is one of the best tools available to implement this type offunctionality.

So, if System A is sending my application a PDF, I can convert it to a PostScript file on the fly!

gs.exe -dNOPAUSE -dBATCH -sDEVICE=pswrite -dLanguageLevel=3 -sOutputFile=WiBit.ps WiBit.pdf

PDF
pdf

PostScript
ps

Alright! The above images show how I converted from one document format to another. I simply executed the GS.EXE command and passed the parameters to handle the conversion. So, what if I wanted to integrate this into an application?

Many developers implement GhostScript by building dynamic commands and executing them on the command shell. This is just my opinion, but I believe if you can avoid launching processes then you should. I have seen software that has a lot of interdependent process executions and sometimes the software becomes very unstable. For example, I once had to support a program that launched a program, that launched a program, that launched a program, etc… It was terrible! Every once-in-a-while this tree of dependencies got screwed up and the application simply sat there in a hung state. This was early on in my career, so I developed my philosophy before I got involved in any serious software development. I don’t often launch executables from code unless I have no alternative. Having said that, there is really nothing wrong with doing it, BUT just try to be smart about it :-).

When I was tasked to develop a software application that needed to handle image manipulation I went right to GhostScript. I wanted to integrate this in a C# .Net Web Service API and I was NOT going to launch a separate process to handle the conversion. So the solution that I came up with was to write a class the wraps the GhostScript API.

Inside the GhostScript /bin folder is a series of executable files.

C:\Program Files\gs\gs9.02\bin>dir
 Volume in drive C has no label.
 Volume Serial Number is FFFF-0000

 Directory of C:\Program Files\gs\gs9.02\bin

11/19/2011  03:06 PM    <DIR>          .
11/19/2011  03:06 PM    <DIR>          ..
04/11/2011  12:14 PM           170,496 gs.exe
04/11/2011  12:14 PM        16,649,216 gsdll32.dll
04/11/2011  12:14 PM             7,424 gsdll32.lib
04/11/2011  12:14 PM           170,496 gswin32.exe
04/11/2011  12:14 PM           161,280 gswin32c.exe
               5 File(s)     17,158,912 bytes
               2 Dir(s)  122,350,276,608 bytes free

The gsdll32.dll is what we’re interested in. This DLL contains all of the functionality for GhostScript. So, instead of launching gswin32.exe to run your conversion you can reference the DLL into your .Net application. Now, keep in mind that this DLL was written in C, so you cannot import it as a .Net reference. You need to apply the [DllExport] directive to reference the external functions. Since GS is open source, you can access the source code to learn how you can best use the API or you can read the documentation that is available on the GhostScript website.

Here is a C# Class that wraps the API:

using System;
using System.Runtime.InteropServices;
using System.Collections;

namespace WiBit.Net
{
    public class WGhostScript
    {
        // Import GS Dll
        [DllImport("gsdll32.dll")]
        private static extern int gsapi_new_instance(out IntPtr pinstance, IntPtr caller_handle);

        [DllImport("gsdll32.dll")]
        private static extern int gsapi_init_with_args(IntPtr instance, int argc, IntPtr argv);

        [DllImport("gsdll32.dll")]
        private static extern int gsapi_exit(IntPtr instance);

        [DllImport("gsdll32.dll")]
        private static extern void gsapi_delete_instance(IntPtr instance);

        // Set variables to be used in the class
        private ArrayList _gsParams = new ArrayList();
        private IntPtr _gsInstancePtr;
        private GCHandle[] _gsArgStrHandles = null;
        private IntPtr[] _gsArgPtrs = null;
        private GCHandle _gsArgPtrsHandle;

        public WGhostScript() { }
        public WGhostScript(string[] Params)
        {
            _gsParams.AddRange(Params);
            Execute();
        }

        public string[] Params
        {
            get { return (string[])_gsParams.ToArray(typeof(string)); }
        }

        public void AddParam(string Param) { _gsParams.Add(Param); }
        public void RemoveParamAtIndex(int Index) { _gsParams.RemoveAt(Index); }
        public void RemoveParam(string Param) { _gsParams.Remove(Param); }

        public void Execute()
        {
            // Create GS Instance (GS-API)
            gsapi_new_instance(out _gsInstancePtr, IntPtr.Zero);
            // Build Argument Arrays
            _gsArgStrHandles = new GCHandle[_gsParams.Count];
            _gsArgPtrs = new IntPtr[_gsParams.Count];

            // Populate Argument Arrays
            for (int i = 0; i < _gsParams.Count; i++)
            {
                _gsArgStrHandles[i] = GCHandle.Alloc(System.Text.ASCIIEncoding.ASCII.GetBytes(_gsParams[i].ToString()), GCHandleType.Pinned);
                _gsArgPtrs[i] = _gsArgStrHandles[i].AddrOfPinnedObject();
            }

            // Allocate memory that is protected from Garbage Collection
            _gsArgPtrsHandle = GCHandle.Alloc(_gsArgPtrs, GCHandleType.Pinned);
            // Init args with GS instance (GS-API)
            gsapi_init_with_args(_gsInstancePtr, _gsArgStrHandles.Length, _gsArgPtrsHandle.AddrOfPinnedObject());
            // Free unmanaged memory
            for (int i = 0; i < _gsArgStrHandles.Length; i++)
                _gsArgStrHandles[i].Free();
            _gsArgPtrsHandle.Free();

             // Exit the api (GS-API)
            gsapi_exit(_gsInstancePtr);
             // Delete GS Instance (GS-API)
            gsapi_delete_instance(_gsInstancePtr);
        }
    }
}

If you study the code a bit you can see that it is really straight-forward. All that this code is doing is accepting a similar set of parameters from the client code in a similar way as the executable file does. The beauty of this is that you are not spawning a separate process when you launch the conversion. In the code we are invoking functions encapsulated in the GhostScript DLL API.

gsapi_new_instance
    This function creates a pointer to a memory location that contains the GhostScript instance to use. This is unique with each usage of this C# Class and it is stored in the unmanaged portion of memory. The instance is stored as the variable _gsInstancePtr, and it uses the out keyword to allow the variable to beset within the function. You can sort of look at this as the equivalent of executing gs.exe in the command line example above.

gsapi_init_with_args
    Just like the command line example, we need to pass the GhostScript API the parameters to control what it does and how it does it. This is expected to be stored in a Collection (ArrayList) and it is inserted into this class using one of the Constructures or the AddParam methods. This can be compared to the list of parameters that are passed to gs.exe in the command line example above.

gsapi_exit
    This closes the API. It does NOT cleanup the memory, however, so keep this in mind.

gsapi_delete_instance
    This removed the GhostScript instance from memory. By using this, the program will be able to execute this class or multiple instances of this class within the same execution and keep the memory leaks out of the equation. The other instances that are associated with this are the parameters that were sent to the instance, which are NOT deallocated by this method. In the //Free unmanaged memory portion of the code you will see where those memory locations are freed.

In order for this class to work properly, the DLL file must be accessible from the location the program is executing from. This means that the GhostScript bin path is available in the System PATH, or the DLL is copied to the executing directory. The second method is my preference for two reasons. Since the [DllImport] directive has the DLL filename hard coded, you can easily control this program from malfunctioning by manually copying the DLL to the execution path with the expected filename. The other reason is to protect against an upgrade. Sometimes different versions of GhostScript do not act exactly the same, and it is a defensive practice to ensure the exact version of the API that you know works with your application is available to your program.

In order to implement this code in C#, all you need to do is create an instance and pass it the parameters, similar to what we did in the command line example above:

using System;

class GsApiExample
{
    static void Main(string[] args)
    {
        string pdfFilePath = @"C:\temp\WiBit.pdf";
        string psFilePath = pdfFilePath + ".ps";
        int psLanguageLevel = 3;
        WiBit.Net.WGhostScript gs = new WiBit.Net.WGhostScript();
        gs.AddParam("-q");
        gs.AddParam("-dNOPAUSE");
        gs.AddParam("-dBATCH");
        gs.AddParam("-dQUIET");
        gs.AddParam("-sDEVICE=pswrite");
        gs.AddParam("-dLanguageLevel=" + psLanguageLevel.ToString());
        gs.AddParam("-dSetPageSize");
        gs.AddParam("-sOutputFile=" + psFilePath);
        gs.AddParam(pdfFilePath);
        gs.Execute();
    }
}

Boom! Now you’ve cleanly integrated the amazing functionality of GhostScript with your C# Application!

Flex Mobile Survival Guide

How to stop worrying and win the war! Mobile development has been arduous, volatile, and fragmented to say the least.

The Kindle Fire, Nook Color Tablet, and the birth of the eReader+ market

Ladies and gentlemen: come one, come all!