swig(python編)

とりあえずpython2から。
VC9ExpressEditionで本家のcpythonバージョン2.6向けのモジュールをビルドしたメモ。

swigは最初cygwinのものを使ったが、バージョンが1.3.38とちょっと古かったのでswigのサイトでwindows向けビルド(1.3.40)を落としてきた。ついでなので2つのswigの出力結果を比べてみたところわりと違った。たぶん改行コードの違いとバージョンの違いが主だと思うが、最新版のWindowsビルドを使うほうが無難な気がする。


後で使う予定のポリゴンローダを模した練習用のC++コード
main.cpp

#include "loader.h"
#include <iostream>

int main(int argc, char **argv)
{
	Loader l;

	if(!l.read(argv[1])){
		return 1;
	}
        std::cout << l.getPath() << std::endl;
	return 0;
}

loader.h

#ifndef LOADER_H_INCLUDED
#define LOADER_H_INCLUDED

#include <vector>
#include <string>

struct Vertex
{
	float x;
	float y;
	float z;
};

struct Loader
{
	std::vector<Vertex> vertices;
	std::string path;

	bool read(const char *path);
	std::string getPath()const;
	int getVertexCount()const;
	Vertex getVertex(int index)const;
};

#endif // LOADER_H_INCLUDED

loader.cpp

#include "loader.h"

bool Loader::read(const char *_path)
{
	path=_path;

	vertices.push_back(Vertex());
	vertices.back().x=0;
	vertices.back().y=1;
	vertices.back().z=2;

	vertices.push_back(Vertex());
	vertices.back().x=3;
	vertices.back().y=4;
	vertices.back().z=5;

	vertices.push_back(Vertex());
	vertices.back().x=6;
	vertices.back().y=7;
	vertices.back().z=8;

	return true;
}

int Loader::getVertexCount()const
{
	return vertices.size();
}

Vertex Loader::getVertex(int index)const
{
	return vertices[index];
}

std::string Loader::getPath()const
{
	return path;
}

これに対してswigスクリプトインターフェースを書く
loader.i

%module loader
%{
#include "loader.h"
%}
%include "loader.h"

pythonモジュール作成スクリプト
setup.py

# setup.py

from distutils.core import setup, Extension

setup(name="loader",
      py_modules=['loader'], 
      ext_modules=[Extension("_loader",
                     ["loader.i","loader.cpp"],
                     swig_opts=['-c++'],
                  )]
      
)

モジュール作成。

$ python setup.py build
$ ls build/lib.win32-2.6/
_loader.pyd

自動的にswigを呼び出してcモジュールの作成までやってくれる。

テスト
test.py

import loader
import sys

l=loader.Loader()

if not l.read("hoge.mqo"):
    print "fail"
    sys.exit()

for i in xrange(l.getVertexCount()):
    print l.getVertex(i)

print l.getPath()

カレントに_loader.pydをコピーして実行。

$ python test.py
<loader.Vertex; proxy of <Swig Object of type 'Vertex *' at 0x00DD1D60> >
<loader.Vertex; proxy of <Swig Object of type 'Vertex *' at 0x00DD1D40> >
<loader.Vertex; proxy of <Swig Object of type 'Vertex *' at 0x00DD1D60> >

エラー

<Swig Object of type 'std::string *' at 0x00DD1D20>swig/python detected a memory leak of type 'std::string *', no destructor found.

std::stringを返す関数でエラーが出る。

std::string向け

%include "std_string.i"
をloader.iに追加する。

%module loader
%{
#include "loader.h"
%}
%include "std_string.i"
%include "loader.h"

再度ビルドしてからカレントに_loader.pydをコピーして実行。

$ python test.py
<loader.Vertex; proxy of <Swig Object of type 'Vertex *' at 0x00DD1F80> >
<loader.Vertex; proxy of <Swig Object of type 'Vertex *' at 0x00DD1F60> >
<loader.Vertex; proxy of <Swig Object of type 'Vertex *' at 0x00DD1F80> >
hoge.mqo

pythonの__str__メソッド追加例

print出力の
>
をカスタマイズする。

loader.i

%extend Vertex {
    char *__str__()
    {
        static char str[256];
        sprintf(str, "[%f, %f, %f]", $self->x, $self->y, $self->z);
        return str;
    }
};

%extendでc++に無いものをpythonモジュールに追加することができる。
$selfがc++のthisのように振る舞う。
文字列の入れ物がstatic char[]でいいのかどうかは良く判らない。

%extend Vertex {
    std::string __str__()
    {
        std::stringstream ss;
        ss 
            << '[' << $self->x << ',' << $self->y << ',' << $self->z << ']';
        return ss.str();
    }
};

std::string方式。
#include "loader.h"
の下に
#include
を追加しておく。

$ python test.py
[0.000000, 1.000000, 2.000000]
[3.000000, 4.000000, 5.000000]
[6.000000, 7.000000, 8.000000]
hoge.mqo

__str__でprint出力変更に成功。

pythonのgeneratorでstd::vectorを回す

python

for i in xrange(l.getVertexCount()):
    print l.getVertex(i)

for v in l.vertices:
    print v

と書きたい。

swigインターフェースに追加

%include "std_vector.i"
%template(VertexVector) std::vector<Vertex>;

これでvectorにアクセス可能になる。
VertexVectorは名前なので適当に。