ナンクル力学系

学んだ事を書き連ねていこう。

PythonのC拡張でAPIを公開する例

leave a comment »

PythonのC拡張を普通に書くと、その関数などは他のC拡張モジュールから使えない。という訳で他のC拡張から読み込めるようにAPIを公開する必要がある訳だけど、その作り方が少しトリッキーだったので公開してみる。

参考:拡張モジュールに C API を提供すると、NumPyのソース。

(追記。そういえばPython/C拡張の練習で書いたコードがあったので晒す:http://snipplr.com/view/8215/example-of-numpyc-api/ C APIは使ってないけど。)

あんまりPythonのC拡張どころか、C言語もそんなに触って無いのでおかしい所があるかも。ツッコミ大歓迎です!

準備するのは以下のファイル。son.Sonとfather.Fatherというモジュールを作ってFatherのメンバにSonをもってきてみた。

/*sonmodule.h*/
typedef struct {
  PyObject_HEAD
  PyObject *str;
  int num;
} Son;

#define MAKE_SON_NEW(self, type, rt_error)	\
  (self) = (Son *)(type)->tp_alloc((type), 0);	\
  if ((self) == rt_error) { return rt_error; }	\
						\
  (self)->str = PyString_FromString("");	\
  if ((self)->str == rt_error) {		\
    Py_DECREF((self));				\
    return rt_error;				\
  }						\
						\
  (self)->num = 0

#ifdef __SON_MODULE
/* ---------- inclue form sonmodule.c ---------- */
static PyTypeObject SonType;

#else
/* ---------- inclue form othere module to use api ---------- */
/* store all api here */
void **Son_API;
/* set alias for easy call */
#define SonType (*(PyTypeObject *)Son_API[0])
#define Son_info (*(PyObject *(*) (Son *))Son_API[1])

static int _import_son(void)
{
  PyObject *son = PyImport_ImportModule("son");
  PyObject *c_api = NULL;
  if (son == NULL){ return -1; }

  /* load c api */
  c_api = PyObject_GetAttrString(son, "_Son_API");
  if (c_api == NULL) {Py_DECREF(son); return -1;}
  if (PyCObject_Check(c_api)) {
    Son_API = (void **)PyCObject_AsVoidPtr(c_api);
  }
  Py_DECREF(c_api);
  Py_DECREF(son);
  if (Son_API == NULL) return -1;

  return 0;
}

#define import_son()				\
  {						\
    if (_import_son() < 0) {			\
      PyErr_Print();				\
      PyErr_SetString( PyExc_ImportError,	\
		       "son failed to import");	\
      return;					\
    }						\
  }

#endif
/* __SONMODULE */
&#91;/sourcecode&#93;

&#91;sourcecode language='cpp'&#93;
/*sonmodule.c*/
#include <Python.h>
#include "structmember.h"

#define __SON_MODULE
#include "sonmodule.h"
#undef __SON_MODULE

static void
Son_dealloc(Son* self)
{
  Py_XDECREF(self->str);
  self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Son_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  Son *self;
  MAKE_SON_NEW(self, type, NULL);
  return (PyObject *)self;
}

static int
Son_init(Son *self, PyObject *args, PyObject *kwds)
{
  PyObject *str=NULL, *tmp;

  static char *kwlist[] = {"str", "num", NULL};

  if (! PyArg_ParseTupleAndKeywords(args, kwds, "|Si", kwlist,
				    &str,
				    &self->num)){
    return -1;
  }

  if (str) {
    tmp = self->str;
    Py_INCREF(str);
    self->str = str;
    Py_DECREF(tmp);
  }

  return 0;
}

static PyMemberDef Son_members[] = {
  {"num", T_INT, offsetof(Son, num), 0, "Sone num"},
  {NULL}  /* Sentinel */
};

static PyObject *
Son_getstr(Son *self, void *closure)
{
  Py_INCREF(self->str);
  return self->str;
}

static int
Son_setstr(Son *self, PyObject *value, void *closure)
{
  if (value == NULL) {
    PyErr_SetString(PyExc_TypeError, "Cannot delete the str attribute");
    return -1;
  }

  if (! PyString_Check(value)) {
    PyErr_SetString(PyExc_TypeError,
                    "The str attribute value must be a string");
    return -1;
  }

  Py_DECREF(self->str);
  Py_INCREF(value);
  self->str = value;    

  return 0;
}

static PyGetSetDef Son_getseters[] = {
  {"str", (getter)Son_getstr, (setter)Son_setstr, "str", NULL},
  {NULL}  /* Sentinel */
};

static PyObject *
Son_info(Son *self)
{
  PyObject *dic = PyDict_New();
  if (dic == NULL) {
    printf("creating dict failed\n");
    return NULL;
  }
  Py_INCREF(self->str);
  PyDict_SetItemString(dic, "str", self->str);
  PyDict_SetItemString(dic, "num", PyLong_FromLong( self->num ));
  return dic;
}

static PyMethodDef Son_methods[] = {
  {"info", (PyCFunction)Son_info, METH_NOARGS, "return info dic"},
  {NULL}  /* Sentinel */
};

static PyTypeObject SonType = {
  PyObject_HEAD_INIT(NULL)
  0,				/*ob_size*/
  "son.Son",			/*tp_name*/
  sizeof(Son),			/*tp_basicsize*/
  0,				/*tp_itemsize*/
  (destructor)Son_dealloc,	/*tp_dealloc*/
  0,				/*tp_print*/
  0,				/*tp_getattr*/
  0,				/*tp_setattr*/
  0,				/*tp_compare*/
  0,				/*tp_repr*/
  0,				/*tp_as_number*/
  0,				/*tp_as_sequence*/
  0,				/*tp_as_mapping*/
  0,				/*tp_hash */
  0,				/*tp_call*/
  0,				/*tp_str*/
  0,				/*tp_getattro*/
  0,				/*tp_setattro*/
  0,				/*tp_as_buffer*/
  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,	/*tp_flags*/
  "Son objects",		/* tp_doc */
  0,				/* tp_traverse */
  0,				/* tp_clear */
  0,				/* tp_richcompare */
  0,				/* tp_weaklistoffset */
  0,				/* tp_iter */
  0,				/* tp_iternext */
  Son_methods,			/* tp_methods */
  Son_members,			/* tp_members */
  Son_getseters,		/* tp_getset */
  0,				/* tp_base */
  0,				/* tp_dict */
  0,				/* tp_descr_get */
  0,				/* tp_descr_set */
  0,				/* tp_dictoffset */
  (initproc)Son_init,		/* tp_init */
  0,				/* tp_alloc */
  Son_new,			/* tp_new */
};

static PyMethodDef module_methods[] = {
  {NULL}  /* Sentinel */
};

/* ---------- insert object into api array ---------- */
void *Son_API[] = {
  (void *) &SonType,
  (void *) Son_info,
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initson(void)
{
  PyObject *m, *d;
  PyObject *c_api;
  if (PyType_Ready(&SonType) < 0){ return; }

  m = Py_InitModule3("son", module_methods,
		     "Son Class");

  if (m == NULL){ goto err; }

  Py_INCREF(&SonType);
  PyModule_AddObject(m, "Son", (PyObject *)&SonType);

  /* ----- set api to module ----- */
  d = PyModule_GetDict(m);
  if (!d){ goto err; }

  c_api = PyCObject_FromVoidPtr((void *)Son_API, NULL);
  if (c_api == NULL){ goto err; }
  PyDict_SetItemString(d, "_Son_API", c_api);
  Py_DECREF(c_api);

  return;

 err:
  if (!PyErr_Occurred()) {
    PyErr_SetString(PyExc_RuntimeError,
		    "cannot load son module.");
  }
  return;
}
&#91;/sourcecode&#93;

&#91;sourcecode language='cpp'&#93;
/*fathermodule.c*/
#include <Python.h>
#include "structmember.h"

#include "sonmodule.h"

typedef struct {
  PyObject_HEAD
  Son *son;
} Father;

static void
Father_dealloc(Father* self)
{
  Py_XDECREF(self->son);
  self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Father_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  Father *self;

  self = (Father *)type->tp_alloc(type, 0);
  if (self == NULL) { return NULL; }

  MAKE_SON_NEW(self->son, &SonType, NULL);

  return (PyObject *)self;
}

static int
Father_init(Father *self, PyObject *args, PyObject *kwds)
{
  return 0;
}

static PyMemberDef Father_members[] = {
  {NULL}  /* Sentinel */
};

static PyObject *
Father_getson(Father *self, void *closure)
{
  Py_INCREF(self->son);
  return (PyObject *)self->son;
}

static int
Father_setson(Father *self, PyObject *value, void *closure)
{
  if (value == NULL) {
    PyErr_SetString(PyExc_TypeError, "Cannot delete the son attribute");
    return -1;
  }

  if (! PyObject_TypeCheck(value, &SonType)) {/* SonType is from Son_API! */
    PyErr_SetString(PyExc_TypeError,
                    "The str attribute value must be a Son");
    return -1;
  }

  Py_DECREF(self->son);
  Py_INCREF(value);
  self->son = (Son *)value;

  return 0;
}

static PyGetSetDef Father_getseters[] = {
  {"son", (getter)Father_getson, (setter)Father_setson, "son", NULL},
  {NULL}  /* Sentinel */
};

static PyObject *
Father_info(Father *self)
{
  PyObject *dic = PyDict_New();
  if (dic == NULL) {
    printf("creating dict failed\n");
    return NULL;
  }
  PyDict_SetItemString(dic, "son",
		       Son_info(self->son));/* Son_info is from Son_API! */
  return dic;
}

static PyMethodDef Father_methods[] = {
  {"info", (PyCFunction)Father_info, METH_NOARGS, "return info dic"},
  {NULL}  /* Sentinel */
};

static PyTypeObject FatherType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "father.Father",             /*tp_name*/
    sizeof(Father),             /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)Father_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "Father objects",           /* tp_doc */
    0,		               /* tp_traverse */
    0,		               /* tp_clear */
    0,		               /* tp_richcompare */
    0,		               /* tp_weaklistoffset */
    0,		               /* tp_iter */
    0,		               /* tp_iternext */
    Father_methods,             /* tp_methods */
    Father_members,             /* tp_members */
    Father_getseters,           /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Father_init,      /* tp_init */
    0,                         /* tp_alloc */
    Father_new,                 /* tp_new */
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

#ifndef PyMODINIT_FUNC	/* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initfather(void)
{
  PyObject* m;
  if (PyType_Ready(&FatherType) < 0){ return; }

  m = Py_InitModule3("father", module_methods,
		     "Father Class");

  if (m == NULL){ return; }

  Py_INCREF(&FatherType);
  PyModule_AddObject(m, "Father", (PyObject *)&FatherType);

  import_son();
}
&#91;/sourcecode&#93;

&#91;sourcecode language='python'&#93;
#setup.py
import sys
import os
from distutils.core import setup, Extension

son_module = Extension(
    'son',
    define_macros = &#91;&#93;,
    include_dirs = &#91;&#93;,
    libraries = &#91;&#93;,
    library_dirs = &#91;&#93;,
    extra_compile_args = &#91;&#93;,
    sources = &#91;'sonmodule.c'&#93;)

father_module = Extension(
    'father',
    define_macros = &#91;&#93;,
    include_dirs = &#91;&#93;,
    libraries = &#91;&#93;,
    library_dirs = &#91;&#93;,
    extra_compile_args = &#91;&#93;,
    sources = &#91;'fathermodule.c'&#93;)

setup(
    name = 'storage',
    version = '1.0',
    description = '',
    ext_modules = &#91;son_module, father_module&#93; )
&#91;/sourcecode&#93;

コンパイルは下のコマンドを使えばソースがあるディレクトリにモジュール(son.so、father.so)が出来る

&#91;sourcecode language='python'&#93;
python setup.py build build_ext --inplace
&#91;/sourcecode&#93;

実行結果はこんな感じ

&#91;sourcecode language='python'&#93;
>>> import son
>>> sn = son.Son()
>>> import father
>>> fr = father.Father()
>>> sn.num = 12
>>> sn.str = "son str"
>>> sn.info()
{'num': 12L, 'str': 'son str'}
>>> fr.son
<son.Son object at 0xb7cd5070>
>>> fr.son.info()
{'num': 0L, 'str': ''}
>>> fr.info()
{'son': {'num': 0L, 'str': ''}}
>>> fr.son = sn
>>> fr.info()
{'son': {'num': 12L, 'str': 'son str'}}

以下、説明。

まず、Cの関数はモジュール間で共有出来ないので、一度PyCObject_FromVoidPtrでPyCObjectにする。

sonmodule.cのPyMODINIT_FUNC initson(void)の中:

 /* ----- set api to module ----- */
  d = PyModule_GetDict(m);
  if (!d){ goto err; }

  c_api = PyCObject_FromVoidPtr((void *)Son_API, NULL);
  if (c_api == NULL){ goto err; }
  PyDict_SetItemString(d, "_Son_API", c_api);
  Py_DECREF(c_api);

これでsonモジュールに_Son_API(son._Son_API)が出来たので、fatherを初期化する時にsonをインポートして_Son_APIを取り出せば良い。それをやってるのが次の部分。

sonmodule.hのstatic int _import_son(void)の中:

  /* load c api */
  c_api = PyObject_GetAttrString(son, "_Son_API");
  if (c_api == NULL) {Py_DECREF(son); return -1;}
  if (PyCObject_Check(c_api)) {
    Son_API = (void **)PyCObject_AsVoidPtr(c_api);
  }

これをラップしたマクロimport_son()をfathre.cのPyMODINIT_FUNC initfather(void)の中で呼んでいる。

このままでは使いにくいので、Son_APIの中身をマクロを使って使いやすい形にしてるのがsonmodule.hの次の部分:

/* set alias for easy call */
#define SonType (*(PyTypeObject *)Son_API[0])
#define Son_info (*(PyObject *(*) (Son *))Son_API[1])

っていう流れでAPIが公開出来ましたとさ!

拡張モジュールに C API を提供するにまんまの資料があることに気づいたのはNumPyのソースを見てプログラム書いて、この記事書いてその後だというのは内緒です。

Written by tkf

September 18, 2008 at 10:42 pm

Posted in programming

Tagged with , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: