extending Python with C anywhere (was Re: Extending Python with C/C++ on a win32 platform)

Alex Martelli aleaxit at yahoo.com
Wed May 23 09:31:55 EDT 2001


"nomad" <nomad***@***freemail.absa.co.za> wrote in message
news:r84ngtod7bv9qsnevj2sou9sm10a8umqa0 at 4ax.com...
    ...
> Are there any other good tutorials, or examples, that are
> aimed at developing extension win32, or even better VC++6?  Also, if I

Easiest is the Boost Python Library at www.boost.org.  It comes
with a small tutorial based on a few simple examples, but the
key fact is that it makes it much easier and more productive
to write Python extensions in C++ (standard-compliant, like for
all of Boost -- but MSVC++, and gcc, while not standard-compliant,
tend to be special-cased:-) than with the C-level Python API.

Still, if you DO want to use the C-level API, it's not rocket
science... just a documentation problem.  Building stuff under
windows has become wonderfully easy thanks to the distutils!-)


> could work my way through some complete source code, that would also
> help alot - if anybody has some working VC++ extensio code out there
> and wouldn't mind sending it, I'd be happy to read through it...

Here's a toy-level example, fully portable to VC++ and
all other Python-supported platforms.  It's an extension
model called "bill" exposing a single function named "bill"
which makes an object of type "bill".  The type is more
or less a C-level version of a typical variant of the
ever-popular "Bunch of named attributes" class, cfr e.g.
http://aspn.activestate.com/ASPN/Python/Cookbook/Recipe/52308
for a Python version.

bill, as here presented, implements in particular a
"creation-time locked" variant: you cannot bind new
attributes after creation time, only rebind or unbind
existing ones (and once one is unbound, you can't
create it again).  Attributes are created when you
call bill.bill with named-arguments, using the name
and initial value you supply.

But the functionality isn't very important -- the key
is, once having a correct, portable "bill.c" file, how
do you build it _everywhere_?

Let's make a bill dictionary and put there, besides
bill.c which we'll see later, mostly setup.py...:

::: setup.py:
import sys
from distutils.core import setup, Extension

bill_ext = Extension('bill', sources=['bill.c'])

setup (name = "bill",
       ext_modules = [ bill_ext ]
)
::: end-of-file

There's MUCH more you could and should put into the
setup.py, but this is the irreducible minimum for
building a C extension.

Once you DO have this, and the correct bill.c...:

D:\PySym\bill>python setup.py install
running install
running build
running build_ext
building 'bill' extension
creating build
creating build\temp.win32-2.1
creating build\temp.win32-2.1\Release
d:\Program Files\Microsoft Visual Studio\VC98\BIN\cl.exe /c /nologo /Ox /MD
/W3
/GX -ID:\Python21\Include /Tcbill.c /Fobuild\temp.win32-2.1\Release\bill.obj
bill.c
creating build\lib.win32-2.1
d:\Program Files\Microsoft Visual Studio\VC98\BIN\link.exe /DLL /nologo
/INCREME
NTAL:NO /LIBPATH:D:\Python21\libs /EXPORT:initbill
build\temp.win32-2.1\Release\
bill.obj /OUT:build\lib.win32-2.1\bill.pyd
/IMPLIB:build\temp.win32-2.1\Release\
bill.lib
   Creating library build\temp.win32-2.1\Release\bill.lib and object
build\temp.
win32-2.1\Release\bill.exp
running install_lib
copying build\lib.win32-2.1\bill.pyd -> D:\Python21

D:\PySym\bill>

There -- a single command did the build AND
installed the .pyd into our \Python21 directory.
Ain't it cool?-)

And now we test (we should really have a unit test
script of course...:-):

D:\PySym\bill>python
Python 2.1 (#15, Apr 16 2001, 18:25:49) [MSC 32 bit (Intel)] on win32
Type "copyright", "credits" or "license" for more information.
>>> import bill
>>> a=bill.bill(x=23,y=45,z=67,t=89)
>>> a.y
45
>>> a.y=29292
>>> a.y
29292
>>> a.q
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: q
>>> a.q=88
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: q
>>> del a.y
>>> a.y
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: y
>>> a.y = 333
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: y
>>>

We're missing a lot of stuff (e.g., a decent repr(),
a way to query a bill object about WHAT attribute
names it knows about, docstrings...), but the idea is
that you can extend or completely recode this for
whatever your purpose is -- whether it be a better
Bunch type, or something completely different:-).

::: bill.c
#include "Python.h"

static PyObject *bill_module;
staticforward PyTypeObject bill_Type;
typedef struct {
    PyObject_HEAD
    PyObject* pdict;
} billObject;

static billObject*
bill_new(PyObject* pdict)
{
    billObject* newobj = PyObject_NEW(billObject, &bill_Type);
    if(!newobj) return 0;
    if(pdict) {
        Py_INCREF(pdict);
        newobj->pdict = pdict;
    } else {
        newobj->pdict = PyDict_New();
    }
    return newobj;
}
static void
bill_dealloc(billObject* oldobj)
{
    Py_DECREF(oldobj->pdict);
    PyMem_DEL(oldobj);
}

static int
bill_setattr(billObject* self, char* name, PyObject* value)
{
    if(!PyDict_GetItemString(self->pdict, name)) {
        PyErr_SetString(PyExc_AttributeError, name);
        return -1;
    }
    if(!value) { // deletion
        return PyDict_DelItemString(self->pdict, name);
    } else { // rebinding
        return PyDict_SetItemString(self->pdict, name, value);
    }
}
static PyObject*
bill_getattr(billObject* self, char* name)
{
    PyObject* result = PyDict_GetItemString(self->pdict, name);
    if(!result) PyErr_SetString(PyExc_AttributeError, name);
    return result;
}

static PyObject *
make_a_bill(PyObject* self, PyObject* args, PyObject* keywords)
{
    return (PyObject*)bill_new(keywords);
}

static PyMethodDef bill_module_functions[] =
{
    { "bill", (PyCFunction)make_a_bill, METH_VARARGS|METH_KEYWORDS },
    { 0, 0 }
};

statichere PyTypeObject bill_Type =
{
    PyObject_HEAD_INIT(0)
    0,                          /* ob_size */
    "bill",                     /* tp_name */
    sizeof(billObject),         /* tp_basicsize */
    0,                          /* tp_itemsize */
    /* methods */
    (destructor) bill_dealloc,  /* tp_dealloc */
    0,                          /* tp_print */
    (getattrfunc) bill_getattr, /* tp_getattr */
    (setattrfunc) bill_setattr, /* tp_setattr */
    0,                          /* tp_compare */
    0, //(reprfunc) bill_repr,       /* tp_repr */
    0,                          /* tp_as_number */
    0,                          /* tp_as_sequence */
    0,                          /* tp_as_mapping */
    0,                          /* tp_hash */
    0,                          /* tp_call */
    0, //(reprfunc) bill_str,        /* tp_str */
    0,                          /* tp_getattro */
    0,                          /* tp_setattro */
    0,                          /* tp_as_buffer */
    0,                          /* tp_flags */
    "bill lightweight structure",
};

void
initbill(void)
{
    bill_Type.ob_type = &PyType_Type;
    bill_module = Py_InitModule("bill", bill_module_functions);
}
::: end-of-file


The ONLY MSVC++ specific trick here (which doesn't hurt
on other compilers!-) is to start the type object with:
    PyObject_HEAD_INIT(0)
and fix it in the module init function with:
    bill_Type.ob_type = &PyType_Type;
while most docs still recommend an idiom that doesn't
work on MSVC++ (no comment on who's at fault:-):
    PyObject_HEAD_INIT(&PyType_Type)

But I see that some of the docs are updated to suggest
the two-steps initialization for portability, anyway.


There -- hope this helps!


Alex






More information about the Python-list mailing list