2011/06/26

Shibokenを用いたCPython用バインディングの生成について

ShibokenはC++ライブラリからCPython用バインディングを生成するPySide付属のユーティリティです。本来はPySideプロジェクトが、QtのCPython用バインディングを作るためのツールですが他のC++ライブラリにも適用できます。

そこで、本記事ではShibokenを用いて既存のC++ライブラリのバインディングを作成し、それを用いるまでの手順を簡単に説明します。なお、環境はMac OS XでApple公式のPythonを用いています。

Shibokenの概要

Shibokenは次の3つから構成されています。

  • API ExtractorはPySideで使われているバインディング生成ライブラリです。ライブラリヘッダを解析した情報と型情報が書かれたXMLファイルの情報を統合して、選択した言語のバインディングを生成します。
  • Generator RunnerはAPI Extractorのコマンドラインフロントエンドです。
  • ShibokenはC++ライブラリからCPython用バインディングを生成するGenerator Runnerプラグインです。

また、生成の過程を表したものが上の図です。設定ファイルや型情報ファイルを与えると、ヘッダファイル群を読み込んでPython用のバインディングが作られます。後はこれをコンパイルしてやればPythonで利用できるようになります。

libraw-liteのPythonバインディング作成

LibRaw-LiteはデジタルカメラのRawファイルを扱うためのC++ライブラリです。今回はこのライブラリをPythonで使えるようにします。

まずはライブラリを解凍して、コンパイルします。

> make

次に、libraw.h内のLibRaw::addmaskedborderstobitmap()はヘッダには宣言されていますが、定義が存在しないために削除します。 そうしなければ、pythonでimportしたときに次のようなエラーになります。

ImportError: dlopen(./libraw.so, 2): Symbol not found: __ZN6LibRaw28add_masked_borders_to_bitmapEv
  Referenced from: /Users/foobar/src/LibRaw-Lite-0.7.2/python/libraw/libraw.so
  Expected in: flat namespace

そして、shibokenがライブラリのヘッダファイルを簡単に取り込めるようにするため、必要なヘッダをまとめたファイルを作成します。

> ls libraw/*.h | awk '{printf("#include \"%s\"\n", $1)}' > shiboken.h

続いて、型情報を記述するためのファイルshiboken_typesystem.xmlを用意します。 これは次のようなファイルになっており、typesystemのpackageでは出力時のpythonのパッケージ名をlibrawを指定しています。また、primitive-typeは基本型を指定し、object-typeはC++の型をターゲット言語(ここではPython)にマッピングさせるようにしています。

> cat shiboken_typesystem.xml
<?xml version="1.0"?>
<typesystem package="libraw">
  <primitive-type name="char"/>
  <primitive-type name="int"/>
  <primitive-type name="float"/>
  <primitive-type name="double"/>
  <primitive-type name="unsigned" />
  <primitive-type name="unsigned int" />
  <primitive-type name="size_t" />
  <object-type name="LibRaw" />
</typesystem>

shiboken_binding.txtはshibokenのコマンドラインオプションを指定するファイルです。先の2ファイルを指定しています。

> cat shiboken_binding.txt
[generator-project]
header-file = shiboken.h
typesystem-file = shiboken_typesystem.xml
output-directory = python
generator-set = /usr/local/lib/generatorrunner/shiboken_generator.dylib
enable-parent-ctor-heuristic
use-isnull-as-nb_nonzero

これでとりあえずバインディングを生成することができます。わからない型を含む関数などは無視されます。

> shiboken --project-file=shiboken_binding.txt
(中略)
Resolving typedefs...                        [WARNING]
Fixing class inheritance...                  [WARNING]
    skipping function 'LibRaw::gamma_lut', unmatched parameter type 'ushort[0x10000]'
    skipping function 'LibRaw::jpeg_thumb', unmatched parameter type 'FILE*'
    skipping function 'LibRaw::write_ppm_tiff', unmatched parameter type 'FILE*'
    skipping function 'LibRaw::dcraw_make_mem_thumb', unmatched return type 'libraw_processed_image_t*'
(中略)
Detecting inconsistencies in class model...  [OK]
Detecting inconsistencies in typesystem...   [OK]

Done, 29 warnings (0 known issues)
(後略)

> cd python/libraw
> ls   
libraw_abstract_datastream_wrapper.cpp    libraw_output_params_t_wrapper.h
libraw_abstract_datastream_wrapper.h    libraw_processed_image_t_wrapper.cpp
libraw_data_t_wrapper.cpp        libraw_processed_image_t_wrapper.h
libraw_data_t_wrapper.h            libraw_python.h
libraw_module_wrapper.cpp        libraw_wrapper.cpp
libraw_output_params_t_wrapper.cpp    libraw_wrapper.h

この出力されたソースを用いてPython拡張のための共有オブジェクトを作成します。

> g++ -fPIC -I/System/Library/Frameworks/Python.framework/Headers \
    -I/usr/local/include/shiboken -I../../libraw -bundle \
    -o libraw.so *.cpp -undefined dynamic_lookup \
    /usr/local/lib/libshiboken-python2.7.1.0.2.dylib ../../lib/libraw-lite.a

これで、Libraw-Liteの一部の機能は利用できるようになります。次の例ではRawファイルを読み込んでその中に含まれるサムネイルを取り出しています。

> file P5039940.ORF
P5039940.ORF: data
> python
Python 2.6.6 (r266:84292, Jan 22 2011, 22:32:56) 
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import libraw
>>> dir(libraw)
['LibRaw', '_Cpp_Api', '__doc__', '__file__', '__name__', '__package__']
>>> r=libraw.LibRaw()
>>> dir(r)
['FC', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', 
'__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', 
'__subclasshook__', 'adjust_sizes_info_only', 'cameraCount', 'cameraList', 'dcraw_document_mode_processing', 
'dcraw_ppm_tiff_writer', 'dcraw_process', 'dcraw_thumb_writer', 'fc', 'open_buffer', 'open_file', 
'recycle', 'strerror', 'unpack', 'unpack_function_name', 'unpack_thumb', 'verbose', 'version', 'versionNumber']
>>> r.version()
'0.7.2-Release(Lite)'
>>> r.open_file('P5039940.ORF')
0
>>> r.unpack_thumb()
0
>>> r.dcraw_thumb_writer('thumb.jpg')
0
>>> exit()
> file thumb.jpg 
thumb.jpg:    JPEG image data, EXIF standard

実際には、このままでは実行すると落ちてしまう (LibRaw.cameraListなど) 場合も多く、修正が必要です。

ただし、Shibokenでは、型システムファイル(shiboken_typesystem.xml)にそれらのソースの修正を書くことができるため、メンテナンス性も高くなっています。

なお、コード修正の方法についてはShibokenのマニュアルやShibokenソース内にあるサンプルファイルなどを参照してください。

まとめ

Shibokenを用いて既存のC++ライブラリのバインディングを作成して利用する方法を説明しました。

関連情報

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。