Open Source Virtual (Network) Printer for Windows that allows you to create PDFs, OCR text, and print images, with advanced features usually available only in enterprise solutions.
NativeMethods.InitAPI64 method throws Runtime Exception when east-asian characters are included in GhostScript paramters. I find the solution is forcing GhostScript APIs to receive UTF-8 strings as arguments. To achieve this, amendment towards two files are required:
NativeMethod.cs:
using System;
using System.Runtime.InteropServices;
namespace clawSoft.clawPDF.Core.Ghostscript
{
internal static class NativeMethods
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern bool SetDllDirectory(string lpPathName);
/*
This code was adapted from Matthew Ephraim's Ghostscript.Net project -
external dll definitions moved into NativeMethods to
satisfy FxCop requirements
https://github.com/mephraim/ghostscriptsharp
*/
#region Hooks into Ghostscript DLL
[DllImport("gsdll32.dll", EntryPoint = "gsapi_new_instance")]
internal static extern int CreateAPIInstance32(out IntPtr pinstance, IntPtr caller_handle);
[DllImport("gsdll32.dll", EntryPoint = "gsapi_set_arg_encoding")]
internal static extern int gsapi_set_arg_encoding32(IntPtr instance, int encoding); // force encoding
[Obsolete]
[DllImport("gsdll32.dll", EntryPoint = "gsapi_init_with_args")]
internal static extern int InitAPI32(IntPtr instance, int argc, string[] argv);
[DllImport("gsdll32.dll", EntryPoint = "gsapi_init_with_args")]
internal static extern int InitAPI32(IntPtr instance, int argc, IntPtr[] argv); // modified caller signature
[DllImport("gsdll32.dll", EntryPoint = "gsapi_exit")]
internal static extern int ExitAPI32(IntPtr instance);
[DllImport("gsdll32.dll", EntryPoint = "gsapi_delete_instance")]
internal static extern void DeleteAPIInstance32(IntPtr instance);
[DllImport("gsdll64.dll", EntryPoint = "gsapi_new_instance")]
internal static extern int CreateAPIInstance64(out IntPtr pinstance, IntPtr caller_handle);
[DllImport("gsdll64.dll", EntryPoint = "gsapi_set_arg_encoding")]
internal static extern int gsapi_set_arg_encoding64(IntPtr instance, int encoding); // ditto
[Obsolete]
[DllImport("gsdll64.dll", EntryPoint = "gsapi_init_with_args")]
internal static extern int InitAPI64(IntPtr instance, int argc, string[] argv);
[DllImport("gsdll64.dll", EntryPoint = "gsapi_init_with_args")]
internal static extern int InitAPI64(IntPtr instance, int argc, IntPtr[] argv); // ditto
[DllImport("gsdll64.dll", EntryPoint = "gsapi_exit")]
internal static extern int ExitAPI64(IntPtr instance);
[DllImport("gsdll64.dll", EntryPoint = "gsapi_delete_instance")]
internal static extern void DeleteAPIInstance64(IntPtr instance);
#endregion Hooks into Ghostscript DLL
}
}
GhostScriptANY.cs
using System;
using System.Runtime.InteropServices;
namespace clawSoft.clawPDF.Core.Ghostscript
{
internal class GhostScriptANY
{
/// <summary>
/// GS can only support a single instance, so we need to bottleneck any multi-threaded systems.
/// </summary>
private static readonly object resourceLock = new object();
private static readonly int GS_ARG_ENCODING_LOCAL = 0;
private static readonly int GS_ARG_ENCODING_UTF8 = 1;
private static readonly int GS_ARG_ENCODING_UTF16LE = 2;
/*
This code was adapted from Matthew Ephraim's Ghostscript.Net project
external dll definitions moved into NativeMethods to
satisfy FxCop requirements
https://github.com/mephraim/ghostscriptsharp
*/
/// <summary>
/// Calls the Ghostscript API with a collection of arguments to be passed to it
/// </summary>
public static void CallAPI(string[] args)
{
// Get a pointer to an instance of the Ghostscript API and run the API with the current arguments
lock (resourceLock)
{
if (System.Environment.Is64BitProcess)
{
CallAPI64(args);
}
else
{
CallAPI32(args);
}
}
}
/// <summary>
/// Call API when Process is 64-bit
/// </summary>
private static void CallAPI64(string[] args)
{
IntPtr gsInstancePtr;
bool memoryAllocated = true;
IntPtr[] nativeStrings = ManagedStringsToNativeUtf8Strings(args, ref memoryAllocated);
try
{
if (!memoryAllocated)
{
throw new OutOfMemoryException("fail to allocate memory for UTF-8 native strings");
}
NativeMethods.CreateAPIInstance64(out gsInstancePtr, IntPtr.Zero);
NativeMethods.gsapi_set_arg_encoding64(gsInstancePtr, GS_ARG_ENCODING_UTF8);
try
{
var result = NativeMethods.InitAPI64(gsInstancePtr, nativeStrings.Length, nativeStrings);
if (result < 0) throw new ExternalException("Ghostscript conversion error", result);
}
finally
{
NativeMethods.ExitAPI64(gsInstancePtr);
NativeMethods.DeleteAPIInstance64(gsInstancePtr);
}
}
catch (ExternalException ex)
{
throw new ExternalException(ex.Message, ex.ErrorCode);
}
catch (OutOfMemoryException oom)
{
throw new OutOfMemoryException(oom.Message);
}
finally
{
CleanUpNativeStrings(nativeStrings);
}
}
/// <summary>
/// Call API when Process is 32-bit
/// </summary>
private static void CallAPI32(string[] args)
{
IntPtr gsInstancePtr;
bool memoryAllocated = true;
IntPtr[] nativeStrings = ManagedStringsToNativeUtf8Strings(args, ref memoryAllocated);
try
{
if (!memoryAllocated)
{
throw new OutOfMemoryException("fail to allocate memory for UTF-8 native strings");
}
NativeMethods.CreateAPIInstance32(out gsInstancePtr, IntPtr.Zero);
NativeMethods.gsapi_set_arg_encoding32(gsInstancePtr, GS_ARG_ENCODING_UTF8);
try
{
var result = NativeMethods.InitAPI32(gsInstancePtr, nativeStrings.Length, nativeStrings);
if (result < 0) throw new ExternalException("Ghostscript conversion error", result);
}
finally
{
NativeMethods.ExitAPI32(gsInstancePtr);
NativeMethods.DeleteAPIInstance32(gsInstancePtr);
}
}
catch (ExternalException ex)
{
throw new ExternalException(ex.Message, ex.ErrorCode);
}
catch (OutOfMemoryException oom)
{
throw new OutOfMemoryException(oom.Message);
}
finally
{
CleanUpNativeStrings(nativeStrings);
}
}
private static IntPtr[] ManagedStringsToNativeUtf8Strings(string[] args, ref bool success)
{
success = true;
if (null == args)
{
throw new ArgumentNullException("args");
}
IntPtr[] result = new IntPtr[args.Length];
for (int i = 0, len = args.Length; i < len; ++i)
{
result[i] = IntPtr.Zero;
}
for (int i = 0, len = args.Length; i < len; ++i)
{
try
{
byte[] byteBuffer = System.Text.Encoding.UTF8.GetBytes(args[i]);
IntPtr ptr = Marshal.AllocHGlobal(sizeof(byte) * (byteBuffer.Length + 1));
result[i] = ptr;
Marshal.Copy(byteBuffer, 0, ptr, byteBuffer.Length); // copy all bytes to unmanaged memory block
Marshal.WriteByte(ptr, byteBuffer.Length, 0); // don't forget to set last byte to null('\0') terminator!
}
catch (OutOfMemoryException oom)
{
success = false;
return result;
}
}
return result;
}
private static void CleanUpNativeStrings(IntPtr[] unmanagedStrings)
{
if (null == unmanagedStrings)
{
throw new ArgumentNullException("unmanagedStrings");
}
foreach (IntPtr unmanagedString in unmanagedStrings)
{
Marshal.FreeHGlobal(unmanagedString);
}
}
}
}
NativeMethods.InitAPI64 method throws Runtime Exception when east-asian characters are included in GhostScript paramters. I find the solution is forcing GhostScript APIs to receive UTF-8 strings as arguments. To achieve this, amendment towards two files are required: NativeMethod.cs:
GhostScriptANY.cs