GhostscriptのDLLをC#から呼び出す
今の仕事で、PostscriptファイルをPDFへ変換するルーチンを書いてるわけです。
んで、最初はProcessを作って、「gswin32c.exe」を呼び出そうとしていたんだけど、どうも連続実行させると調子が悪い。(逝きっぱなしになる)
メモリ関連かなとも思うんだけど、調査しきれず。
んで、せっかくオープンソース物だし、上記EXEも一緒についてくるDLLのフロントエンドらしいし、DLL直で呼び出せばインプロセスで動くし、メモリ系の問題があっても調査しやすいだろうと言うことで、こちらを試してみました。
結論から言うとうまくいったんだけどね。
マネージ(アプリケーション) → アンマネージ(DLL)
のパラメータマーシャリングというか、ポインタのポインタをどうやって渡すかとか。
まぁ、いい勉強になったわ。
つー事で、サンプル公開。
/// Ghostscriptからの標準出力用デリゲート宣言 /// int(*stdin_fn)(void *caller_handle, char *buf, int len) public delegate int GSStdin(System.IntPtr caller, StringBuilder str, System.Int32 len); /// int(*stdout_fn)(void *caller_handle, const char *str, int len) public delegate int GSStdout(System.IntPtr caller, String str, System.Int32 len); /// int(*stderr_fn)(void *caller_handle, const char *str, int len) public delegate int GSStderr(System.IntPtr caller, String str, System.Int32 len); public class PDFMaker { ////// PDFを生成します。 /// /// 入力されるPostscriptファイル名 /// 出力するPDFファイル名 public bool makePdf(String infile, String outfile) { String cmds = new String[14]; cmds[0] = this.GSCOMMAND; cmds[1] = this.MakeIncludePath(); cmds[2] = "-dSAFER"; cmds[3] = "-dNOPAUSE"; cmds[4] = "-dBATCH"; cmds[5] = "-sDEVICE#pdfwrite"; cmds[6] = "-dAutoRotatePages#/PageByPage"; cmds[7] = "-dPDFFitPage"; cmds[8] = "-sOutputFile=" + outfile; cmds[9] = "-dCompatibilityLevel=" + this.GetPDFVersionString(); cmds[10] = "-c"; cmds[11] = ".setpdfwrite"; cmds[12] = "-f"; cmds[13] = infile; int ret = this.CallGS(cmds); if( 0 != ret) { return false; } return true; } private String MakeIncludePath() { Ps2PdfSetting p2p = new Ps2PdfSetting(); // 設定を持ってるクラスがあると思ってくれ。 String gs851base = Directory.GetParent(p2p.GSPath).Parent.FullName; String gsbase = Directory.GetParent(gs851base).FullName; String fontPath = p2p.WinFontPath; String incpath = gs851base + @"\Resource"; incpath += ";"; incpath += gs851base + @"\lib"; incpath += ";"; incpath += gs851base + @"\kanji"; incpath += ";"; incpath += gsbase + @"\fonts"; incpath += ";"; incpath += fontPath; incpath = "-I\"" + incpath + "\""; return incpath; } ///int gsapi_new_instance (void **pinstance, void *caller_handle); [DllImport("gsdll32.dll" , EntryPoint="gsapi_new_instance")] unsafe public static extern int gsapi_new_instance( [In,Out]System.IntPtr* inst, [In]System.IntPtr p ); /// int gsapi_init_with_args (void *instance, int argc, char **argv); [DllImport("gsdll32.dll" , EntryPoint="gsapi_init_with_args")] unsafe public static extern int gsapi_init_with_args( [In]System.IntPtr inst, [In]System.Int32 argc, [In]String cmd ); /// int gsapi_exit (void *instance); [DllImport("gsdll32.dll" , EntryPoint="gsapi_exit")] unsafe public static extern int gsapi_exit( [In]System.IntPtr inst ); /// void gsapi_delete_instance (void *instance); [DllImport("gsdll32.dll" , EntryPoint="gsapi_delete_instance")] unsafe public static extern int gsapi_delete_instance( [In]System.IntPtr inst ); /// int gsapi_set_stdio (void *instance, int(*stdin_fn)(void *caller_handle, char *buf, int len), /// int(*stdout_fn)(void *caller_handle, const char *str, int len), /// int(*stderr_fn)(void *caller_handle, const char *str, int len)); [DllImport("gsdll32.dll" , EntryPoint="gsapi_delete_instance")] unsafe public static extern int gsapi_set_stdio( System.IntPtr inst, GSStdin stdin, GSStdout stdout, GSStderr stderr ); private int GsStdOutCallback(System.IntPtr caller, String str, System.Int32 len) { Console.WriteLine(str); return 0; } unsafe private int CallGS(String[] cmdline) { int ret = 0; System.IntPtr inst = new IntPtr(); ret = gsapi_new_instance(&inst, new IntPtr(null)); if(ret != 0) return ret; GSStdout so = new GSStdout(this.GsStdOutCallback); ret = gsapi_set_stdio(inst, null, so, null); if(ret != 0) return ret; ret = gsapi_init_with_args(inst, cmdline.Length, cmdline); gsapi_exit(inst); gsapi_delete_instance(inst); return ret; } }
結局、標準出力にはき出されるGhostscriptからの出力はハンドリングしきれてないんだけどね。
デリゲートをアンマネージに渡すってのが、今のくさってる頭じゃ無理。
いずれやろうかと思います。
コード汚いとか言うな。