Using 64-bits Ghostscript dll with Unity

Hello everyone
I’m trying to integrate the ghostscript dll into my project. I’ve followed the instruction, copying the dll file from ghostscript install path and put it under “Assets/Plugins” in my project folder(please be noted that I’m using a 64-bits version of ghostscript, that makes the dll as “gsdll64.dll”). However when I play my scene, it always give a “DllNotFoundException”. The following is the sample code I found on the Internet(and I’m currently using it) of including the dll file.

using UnityEngine;
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Collections;
/**
Convert PDF to Image Format(JPEG) using Ghostscript API

convert a pdf to jpeg using ghostscript command line:
gswin32c -q -dQUIET -dPARANOIDSAFER  -dBATCH -dNOPAUSE  -dNOPROMPT -dMaxBitmap=500000000 -dFirstPage=1 -dAlignToPixels=0 -dGridFitTT=0 -sDEVICE=jpeg -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -r100x100 -sOutputFile=output.jpg test.pdf
see also:http://www.mattephraim.com/blog/2009/01/06/a-simple-c-wrapper-for-ghostscript/
and: http://www.codeproject.com/KB/cs/GhostScriptUseWithCSharp.aspx
Note:copy gsdll32.dll to system32 directory before using this ghostscript wrapper.
 * 
 */
namespace ConvertPDF
{
    class PDFConvert
    {
        #region GhostScript Import
        /// <summary>Create a new instance of Ghostscript. This instance is passed to most other gsapi functions. The caller_handle will be provided to callback functions.
        ///  At this stage, Ghostscript supports only one instance. </summary>
        /// <param name="pinstance"></param>
        /// <param name="caller_handle"></param>
        /// <returns></returns>
        [DllImport("gsdll64.dll", EntryPoint="gsapi_new_instance")]
        private static extern int gsapi_new_instance (out IntPtr pinstance, IntPtr caller_handle);
        /// <summary>This is the important function that will perform the conversion</summary>
        /// <param name="instance"></param>
        /// <param name="argc"></param>
        /// <param name="argv"></param>
        /// <returns></returns>
        [DllImport("gsdll64.dll", EntryPoint="gsapi_init_with_args")]
        private static extern int gsapi_init_with_args (IntPtr instance, int argc, IntPtr argv);
        /// <summary>
        /// Exit the interpreter. This must be called on shutdown if gsapi_init_with_args() has been called, and just before gsapi_delete_instance(). 
        /// </summary>
        /// <param name="instance"></param>
        /// <returns></returns>
        [DllImport("gsdll64.dll", EntryPoint="gsapi_exit")]
        private static extern int gsapi_exit (IntPtr instance);
        /// <summary>
        /// Destroy an instance of Ghostscript. Before you call this, Ghostscript must have finished. If Ghostscript has been initialised, you must call gsapi_exit before gsapi_delete_instance. 
        /// </summary>
        /// <param name="instance"></param>
        [DllImport("gsdll64.dll", EntryPoint="gsapi_delete_instance")]
        private static extern void gsapi_delete_instance (IntPtr instance);
        #endregion
        #region Variables
        private string _sDeviceFormat;
        private int _iWidth;
        private int _iHeight;
        private int _iResolutionX;
        private int _iResolutionY;
        private int _iJPEGQuality;
        private Boolean _bFitPage;
        private IntPtr _objHandle;
        #endregion
        #region Proprieties
        public string OutputFormat
        {
            get { return _sDeviceFormat; }
            set { _sDeviceFormat = value; }
        }
        public int Width
        {
            get { return _iWidth; }
            set { _iWidth = value; }
        }
        public int Height
        {
            get { return _iHeight; }
            set { _iHeight = value; }
        }
        public int ResolutionX
        {
            get { return _iResolutionX; }
            set { _iResolutionX = value; }
        }
        public int ResolutionY
        {
            get { return _iResolutionY; }
            set { _iResolutionY = value; }
        }
        public Boolean FitPage
        {
            get { return _bFitPage; }
            set { _bFitPage = value; }
        }
        /// <summary>Quality of compression of JPG</summary>
        public int JPEGQuality
        {
            get { return _iJPEGQuality; }
            set { _iJPEGQuality = value; }
        }
        #endregion
        #region Init
        public PDFConvert(IntPtr objHandle)
        {
            _objHandle = objHandle;
        }
        public PDFConvert()
        {
            _objHandle = IntPtr.Zero;
        }
        #endregion
        private byte[] StringToAnsiZ(string str)
        {
            //' Convert a Unicode string to a null terminated Ansi string for Ghostscript.
            //' The result is stored in a byte array. Later you will need to convert
            //' this byte array to a pointer with GCHandle.Alloc(XXXX, GCHandleType.Pinned)
            //' and GSHandle.AddrOfPinnedObject()
            int intElementCount;
            int intCounter;
            byte[] aAnsi;
            byte bChar;
            intElementCount = str.Length;
            aAnsi = new byte[intElementCount+1];
            for(intCounter = 0; intCounter < intElementCount;intCounter++)
            {
                bChar = (byte)str[intCounter];
                aAnsi[intCounter] = bChar;
            }
            aAnsi[intElementCount] = 0;
            return aAnsi;
        }
        /// <summary>Convert the file!</summary>
        public void Convert(string inputFile,string outputFile,
            int firstPage, int lastPage, string deviceFormat, int width, int height)
        {
            //Avoid to work when the file doesn't exist
            if (!System.IO.File.Exists(inputFile))
            {
                Debug.Log("File doesn't exists");
                return;
            }
            int intReturn;
            IntPtr intGSInstanceHandle;
            object[] aAnsiArgs;
            IntPtr[] aPtrArgs;
            GCHandle[] aGCHandle;
            int intCounter;
            int intElementCount;
            IntPtr callerHandle;
            GCHandle gchandleArgs;
            IntPtr intptrArgs;
            string[] sArgs = GetGeneratedArgs(inputFile,outputFile,
                firstPage, lastPage, deviceFormat, width, height);
            // Convert the Unicode strings to null terminated ANSI byte arrays
            // then get pointers to the byte arrays.
            intElementCount = sArgs.Length;
            aAnsiArgs = new object[intElementCount];
            aPtrArgs = new IntPtr[intElementCount];
            aGCHandle = new GCHandle[intElementCount];
            // Create a handle for each of the arguments after 
            // they've been converted to an ANSI null terminated
            // string. Then store the pointers for each of the handles
            for(intCounter = 0; intCounter< intElementCount; intCounter++)
            {
                aAnsiArgs[intCounter] = StringToAnsiZ(sArgs[intCounter]);
                aGCHandle[intCounter] = GCHandle.Alloc(aAnsiArgs[intCounter], GCHandleType.Pinned);
                aPtrArgs[intCounter] = aGCHandle[intCounter].AddrOfPinnedObject();
            }
            // Get a new handle for the array of argument pointers
            gchandleArgs = GCHandle.Alloc(aPtrArgs, GCHandleType.Pinned);
            intptrArgs = gchandleArgs.AddrOfPinnedObject();
            intReturn = gsapi_new_instance(out intGSInstanceHandle, _objHandle);
            callerHandle = IntPtr.Zero;
            try
            {
                intReturn = gsapi_init_with_args(intGSInstanceHandle, intElementCount, intptrArgs);
            }
            catch (Exception ex)
            {
                //System.Windows.Forms.MessageBox.Show(ex.Message);
                Debug.Log("file doesn't exists.");
               
            }
            finally
            {
                for (intCounter = 0; intCounter < intReturn; intCounter++)
                {
                    aGCHandle[intCounter].Free();
                }
                gchandleArgs.Free();
                gsapi_exit(intGSInstanceHandle);
                gsapi_delete_instance(intGSInstanceHandle);
            }
        }
        private string[] GetGeneratedArgs(string inputFile, string outputFile,
            int firstPage, int lastPage, string deviceFormat, int width, int height)
        {
            this._sDeviceFormat = deviceFormat;
            this._iResolutionX = width;
            this._iResolutionY = height;
            // Count how many extra args are need - HRangel - 11/29/2006, 3:13:43 PM
            ArrayList lstExtraArgs = new ArrayList();
            if ( _sDeviceFormat=="jpg" && _iJPEGQuality > 0 && _iJPEGQuality < 101)
                lstExtraArgs.Add("-dJPEGQ=" + _iJPEGQuality);
            if (_iWidth > 0 && _iHeight > 0)
                lstExtraArgs.Add("-g" + _iWidth + "x" + _iHeight);
            if (_bFitPage)
                lstExtraArgs.Add("-dPDFFitPage");
            if (_iResolutionX > 0)
            {
                if (_iResolutionY > 0)
                    lstExtraArgs.Add("-r" + _iResolutionX + "x" + _iResolutionY);
                else
                    lstExtraArgs.Add("-r" + _iResolutionX);
            }
            // Load Fixed Args - HRangel - 11/29/2006, 3:34:02 PM
            int iFixedCount = 17;
            int iExtraArgsCount = lstExtraArgs.Count;
            string[] args = new string[iFixedCount + lstExtraArgs.Count];
            /*
            // Keep gs from writing information to standard output
        "-q",                    
        "-dQUIET",
       
        "-dPARANOIDSAFER", // Run this command in safe mode
        "-dBATCH", // Keep gs from going into interactive mode
        "-dNOPAUSE", // Do not prompt and pause for each page
        "-dNOPROMPT", // Disable prompts for user interaction          
        "-dMaxBitmap=500000000", // Set high for better performance
       
        // Set the starting and ending pages
        String.Format("-dFirstPage={0}", firstPage),
        String.Format("-dLastPage={0}", lastPage),  
       
        // Configure the output anti-aliasing, resolution, etc
        "-dAlignToPixels=0",
        "-dGridFitTT=0",
        "-sDEVICE=jpeg",
        "-dTextAlphaBits=4",
        "-dGraphicsAlphaBits=4",
            */
            args[0]="pdf2img";//this parameter have little real use
            args[1]="-dNOPAUSE";//I don't want interruptions
            args[2]="-dBATCH";//stop after
            //args[3]="-dSAFER";
            args[3] = "-dPARANOIDSAFER";
            args[4]="-sDEVICE="+_sDeviceFormat;//what kind of export format i should provide
            args[5] = "-q";
            args[6] = "-dQUIET";
            args[7] = "-dNOPROMPT";
            args[8] = "-dMaxBitmap=500000000";
            args[9] = String.Format("-dFirstPage={0}", firstPage);
            args[10] = String.Format("-dLastPage={0}", lastPage);
            args[11] = "-dAlignToPixels=0";
            args[12] = "-dGridFitTT=0";
            args[13] = "-dTextAlphaBits=4";
            args[14] = "-dGraphicsAlphaBits=4";
            //For a complete list watch here:
            //http://pages.cs.wisc.edu/~ghost/doc/cvs/Devices.htm
            //Fill the remaining parameters
            for (int i=0; i < iExtraArgsCount; i++)
            {
                args[15+i] = (string) lstExtraArgs[i];
            }
            //Fill outputfile and inputfile
            args[15 + iExtraArgsCount] = string.Format("-sOutputFile={0}",outputFile);
            args[16 + iExtraArgsCount] = string.Format("{0}",inputFile);
            return args;
        }
        public void pdf2jpgTest()
        {            
            //this.Convert(@"C://tmp//pdfimg//test1.pdf",@"C://tmp//pdfimg//out.jpg",1,1,"jpeg",100,100);
            //this.Convert(@"C://tmp//pdfimg//test.pdf", @"C://tmp//pdfimg//out2.jpg", 291, 291, "jpeg", 800, 800);
           
            this.Convert(@"C:/tempPDF/Sources/sample.pdf",
                        @"C:/tempPDF/Textures/texting.jpg",
                        1,
                        10,
                        "jpeg",
                        800,
                        600);
        }
    }
}

and the following is my code of calling the class

using UnityEngine;
using System.Collections;
using ConvertPDF;

public class testScript : MonoBehaviour {

    // Use this for initialization
    void Start () 
    {
        PDFConvert converter = new PDFConvert();
       
        converter.Convert(@"C:/tempPDF/Sources/sample.pdf",
                         @"C:/tempPDF/Textures/texting.jpg",
                         1,
                         10,
                         "jpeg",
                         800,
                         600);
       
       
    }
}

I’m don’t know what am I missing and have been stuck here for a few days, so any idea or help would be greatly appreciated.

Mixing 32bit (Unity) with 64bit dll’s is not possible right out of the box. Your dll has to be a 32 bit version and it has to be compiled against maximal Net version 3.5

1 Like

arrhhh I see.
Actually I had though about switching to 32-bits ghostscript, but I kept thinking it’ll be pretty stupid if a 64-bits dll doesn’t support a 32-bits application coz I generally think 64-bits is kinda “backward compatible”.

The ghostscript I’m using is downloaded from http://www.ghostscript.com/ , which I believe to be the official website. So I’m not sure whether the dll is compiled against maximal Net version 3.5. I hope it is so.

UPDATE:
just a quick update, after i’ve downloaded and installed a 32-bits ghostscript, modified the script(changing from gsdll64 to gsdll32),and run it, it still gives the DllNotFoundException

Well, I guess that there are other problems with ghostscript then the 32/64 bit problem.I don’t see a net version of ghostscript on the official site. There is a net wrapper library here : http://ghostscriptnet.codeplex.com but it uses some features like unsafe code that has to be handled in a special way with unity. Look the forum for ‘unsafe code’ . Perhaps they use other features that the mono version of unity can’t handle.

Hey thx for the answer, but I finally found the answer. This is extremely stupid.
The correct way to to call the library is

[DllImport("gsdll32", EntryPoint="gsapi_new_instance")]
[DllImport("gsdll32", EntryPoint="gsapi_init_with_args")]
[DllImport("gsdll32", EntryPoint="gsapi_exit")]
[DllImport("gsdll32", EntryPoint="gsapi_delete_instance")]

That is, the dll name there should be without the “.dll”. A lot of example from the Internet has the “.dll” included, which in this case, won’t work.

Cool,
did you write your own NET wrapper or do you use an existing one?

@CipherZero @sloopidoopi Failed to load ‘Assets/Plugins/gsdll32.dll’, expected x64 architecture, but was x86 architecture. You must recompile your plugin for x64 architecture.

Did you ever encounter that error?

Using the gsdll64 fixes the error for me as the x32 is based on x86 architecture and the x64 on x64 arch… weird this didn’t work for you.

Probably because it’s 3 years later, and we’re on a much more recent version of Unity.