自分でビルドしたPoppler 0.22.5 + .NETバインディングがうまく動かなかった

ここ数日、Popplerを自分でビルドしたりして.NETから叩いてみてたんだけど、どうも動作が不安定。

こんな感じのコードが動いたり、AccessViolationException(メモリ破壊)が起こったり、動作が安定しない。

var document = Poppler.Document.NewFromFile("file:///path/to/src.pdf", String.Empty);
var page = document.GetPage(0);

var surface = new Cairo.ImageSurface(Cairo.Format.Argb32, 300, 300);
var cairo = new Cairo.Context(surface);
page.Render(cairo);
surface.WriteToPng("out.png"); // ここで死ぬ

surface.Dispose();
page.Dispose();
document.Dispose();

原因がはっきりしないのだけど、どうもCairoがうまく動いてないみたい。調査をするのも大変だし、ビルドしなおすのも大変(依存ライブラリが多すぎる)ので、とりあえずPopplerを見切って他の物を調べることにした。

Poppler 0.22.5の.NETバインディングを作成する

MinGWでビルドしたPoppler 0.22.5の.NETバインディングを作成する。Windows単体ではすんなり作成できそうにないので、LinuxXubuntu)で作業した。

必要な環境

monoの開発環境。

XubuntoでMonoDevelop 4が動かしたくて色々入れた後なので、どのパッケージが必要かどうかとか確認してない。ちなみにMonoDevelop 4をコンパイルはInstall MonoDevelop 4 under Ubuntu 12.04 LTS from sourcesの手順でやっている。

手順

GAPI - Monoという、GLib依存APIバインディングコードを自動生成するやつを使う。当然、Popplerは--enable-glibを指定してコンパイルしている前提。

  1. Popplerのソースをダウンロードしてきて展開
  2. generaterの設定ファイルを作成
    • .sourcesは後述
  3. gapi2-parserに.sourcesを食わせてAPIファイル(.raw)を生成
    • gapi2-parser poppler-api.sources
  4. gapi2-fixupでAPIファイルにパッチをあてる
    • cp poppler-api.raw poppler-api.xml
    • gapi2-fixup --api=poppler-api.xml --metadata=poppler-api.metadata
    • .metadataは後述
  5. gapi2-codegenにパッチ適用済みのAPIファイルを食わせるコードを生成
  6. コンパイル
    • mcs -pkg:gtk-sharp-2.0 -pkg:mono-cairo -target:library -unsafe -o poppler-sharp.dll generate/*.cs
poppler-api.sources
<gapi-parser-input>
  <api filename="poppler-api.raw">
    <library name="libpoppler-glib-8.dll">
      <namespace name="Poppler">
        <dir>/home/terurou/poppler-0.22.5/glib</dir>
      </namespace>
    </library>
  </api>
</gapi-parser-input>
poppler-api.metadata
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
  <add-node path="/api">
    <symbol type="manual" cname="cairo_t" name="Cairo.Context" />
  </add-node>
  <add-node path="/api">
    <symbol type="manual" cname="GdkPixbuf" name="Gdk.Pixbuf" />
  </add-node>
  <attr path="/api/namespace/enum[@name='Permissions']" name="hidden">1</attr>
  <attr path="/api/namespace/object[@name='Action']" name="parent">GObject</attr>
  <attr path="/api/namespace/object[@name='Annot']" name="parent">GObject</attr>
  <attr path="/api/namespace/struct[@name='AnnotCalloutLine']" name="opaque">false</attr>
  <attr path="/api/namespace/object[@name='AnnotFreeText']" name="parent">GObject</attr>
  <attr path="/api/namespace/struct[@name='AnnotMapping']" name="opaque">false</attr>
  <attr path="/api/namespace/object[@name='AnnotMovie']" name="parent">GObject</attr>
  <attr path="/api/namespace/object[@name='AnnotScreen']" name="parent">GObject</attr>
  <attr path="/api/namespace/object[@name='AnnotText']" name="parent">GObject</attr>
  <attr path="/api/namespace/struct[@name='Color']" name="opaque">false</attr>
  <attr path="/api/namespace/struct[@name='Dest']" name="opaque">false</attr>
  <attr path="/api/namespace/struct[@name='FormFieldMapping']" name="opaque">false</attr>
  <attr path="/api/namespace/struct[@name='ImageMapping']" name="opaque">false</attr>
  <attr path="/api/namespace/struct[@name='LinkMapping']" name="opaque">false</attr>
  <attr path="/api/namespace/object[@name='Media']" name="parent">GObject</attr>
  <attr path="/api/namespace/object[@name='Movie']" name="parent">GObject</attr>
  <attr path="/api/namespace/struct[@name='PageTransition']" name="opaque">false</attr>
  <attr path="/api/namespace/struct[@name='Rectangle']" name="opaque">false</attr>
  <attr path="/api/namespace/struct[@name='TextAttributes']" name="opaque">false</attr>
</metadata>

使用する際の注意

glib-sharpとMono.Cairoに依存したコードが生成されるので、実行する場合はこれ必要になる。Windowsの場合はGTK#をインストールすれば同梱されているので、これを流用する。

補足

Popplerの.NETバインディング自体はPdfModがおそらく同じ方法で作成しているようだ。GPLであることを気にしなければ、これを流用してしまうのもありかもしれない。あと、debian系のディストリビューションではpoppler-sharpという名前でパッケージが配布されている。

自分の場合はMinGWでビルドしたものを使いたい(DllImportのパスをMinGWに合わせなくてはいけない)のと、GPLにはしたくないなぁというのがあったので、自前でコードを生成した。(ちなみにGTK#, glib-sharp, Mono.CairoはLGPL

MinGWでPoppler 0.22.5をビルド

依存ライブラリが多くて疲れた…。ビルドした後から手順を書いてるので間違ってるかも。

インストール手順メモ

  1. MinGW
  2. GLib, gettext, pkg-configのバイナリを/MinGWにぶち込む
  3. Python 2.7.5
    • パスを通しておく。Chocolateyとかで入れると楽。
  4. zlib 1.2.8
  5. libpng 1.6.2
    • ./configure --prefix=/mingw
    • make
    • make install
  6. libjpeg v9
    • libjpeg-turboだとPopplerのconfigureを通せなかった
    • ./configure --prefix=/mingw
    • make
    • make install
  7. XZ Utils 5.0.5
    • ./configure --prefix=/mingw
    • make
    • make install
  8. LibTIFF 4.0.3
    • ./configure --prefix=/mingw
    • make
    • make install
  9. Little CMS 2.5
    • ./configure --prefix=/mingw
    • make
    • make install
  10. OpenJPEG 1.5.1
    • ./configure --prefix=/mingw
    • make
    • make install
  11. freetype 2.5.0
    • ./configure --prefix=/mingw
    • make
    • make install
  12. nkf 2.1.2(fontconfigの確認ためだけなので要らないと思う)
    • make
    • make install --prefix=/mingw
  13. libxml2 2.9.1(git snapshot)
    • ./configure --prefix=/mingw
    • make
    • make install
  14. fontconfig 2.10.93
  15. pixman 0.30.0
    • MinGWではautogenがコケるので、Cairoが配布しているものをダウンロードしてくる。
    • ./configure --prefix=/mingw
    • make
    • make install
  16. Cairo 1.12.14
    • ./configure --prefix=/mingw
    • echo "#define _SSIZE_T_DEFINED 1" >> config.h
    • make
    • make install
  17. libffi 3.0.13
    • ./configure --prefix=/mingw
    • make
    • make install
  18. GLib 2.37.3
    • CFLAGS="-Wall -Ofast -march=native" ./configure --prefix=/mingw
    • make
    • make install
  19. OpenSSL 1.0.1e(Popplerで--enable-libcurlを指定しない場合は不要)
  20. curl 7.31.0(Popplerで--enable-libcurlを指定しない場合は不要)
    • ./configure --prefix=/mingw
    • make
    • make install
  21. poppler 0.22.5
    • ./configure --prefix=/mingw --with-font-configuration=fontconfig --enable-xpdf-headers --enable-zlib --enable-libcurl --enable-poppler-glib --disable-poppler-qt4 --disable-poppler-cpp --disable-gtk-test
    • make
    • make install