cpython/Mac/Demo/plugins.html

325 lines
16 KiB
HTML

<HTML><HEAD><TITLE>Creating a C extension module on the Macintosh</TITLE></HEAD>
<BODY>
<H1>Creating a C extension module on the Macintosh</H1>
<HR>
This document gives a step-by-step example of how to create a new C
extension module on the mac. For this example, we will create a module
to interface to the programmers' API of InterSLIP, a package that
allows you to use MacTCP (and, hence, all internet services) over a
modem connection. The actual example does not work anymore, as both
MacTCP and Interslip died long ago, but the text is still mostly
correct.<p>
<H2>Prerequisites</H2>
There are a few things you need to pull this off. First and foremost,
you need a C development environment. Actually, you need a specific
development environment, CodeWarrior by <A
HREF="http://www.metrowerks.com/">MetroWerks</A>. You will
need Version 7 or later. You may be able to get by with an older
version of CodeWarrior or with another development environment (Up to
about 1994 python was developed with THINK C, and in the dim past it
was compiled with MPW C) assuming you have managed to get Python to
compile under your development environment, but the step-by-step
character of this document will be lost. <p>
Next, you need to install the Developer option in the MacPython installer.
You may also find that Guido's <A
HREF="http://www.python.org/doc/ext/ext.html">Extending and embedding
the Python interpreter</A> is a very handy piece of documentation. I
will skip lots of details that are handled there, like complete
descriptions of <CODE>Py_ParseTuple</CODE> and such utility routines, or
the general structure of extension modules. <p>
<H2>InterSLIP and the C API to it</H2>
InterSLIP, the utility to which we are going to create a python
interface, is a system extension that does all the work of connecting
to the internet over a modem connection. InterSLIP is provided
free-of-charge by <A
HREF="http://www.intercon.com/">InterCon</A>. First it connects to
your modem, then it goes through the whole process of dialling,
logging in and possibly starting the SLIP software on the remote
computer and finally it starts with the real work: packing up IP
packets handed to it by MacTCP and sending them to the remote side
(and, of course, the reverse action of receiving incoming packets,
unpacking them and handing them to MacTCP). InterSLIP is a device
driver, and you control it using a application supplied with it,
InterSLIP Setup. The API that InterSLIP Setup uses to talk to the
device driver is published in the documentation and, hence, also
useable by other applications. <p>
I happened to have a C interface to the API, which is all ugly
low-level device-driver calls by itself. The C interface is in <A
HREF="interslip/InterslipLib.c">InterslipLib.c</A> and <A
HREF="interslip/InterslipLib.h">InterslipLib.h</A>, we'll
concentrate here on how to build the Python wrapper module around
it. Note that this is the "normal" situation when you are writing a
Python extension module: you have some sort of functionality available
to C programmers and want to make a Python interface to it. <p>
<H2>Using Modulator</H2>
The method we describe in this document, using Modulator, is the best
method for small interfaces. For large interfaces there is another
tool, Bgen, which actually generates the complete module without you
lifting a single finger. Bgen, however, has the disadvantage of having
a very steep learning curve, so an example using it will have to wait
until another document, when I have more time. <p>
First, let us look at the <A
HREF="interslip/InterslipLib.h">InterslipLib.h</A> header file,
and see that the whole interface consists of six routines:
<CODE>is_open</CODE>, <CODE>is_connect</CODE>,
<CODE>is_disconnect</CODE>, <CODE>is_status</CODE>,
<CODE>is_getconfig</CODE> and <CODE>is_setconfig</CODE>. Our first
step will be to create a skeleton file <A
HREF="interslip/@interslipmodule.c">@interslipmodule.c</A>, a
dummy module that will contain all the glue code that python expects
of an extension module. Creating this glue code is a breeze with
modulator, a tool that we only have to tell that we want to create a
module with methods of the six names above and that will create the
complete skeleton C code for us. <p>
Why call this dummy module <CODE>@interslipmodule.c</CODE> and not
<CODE>interslipmodule.c</CODE>? Self-preservation: if ever you happen
to repeat the whole process after you have actually turned the
skeleton module into a real module you would overwrite your
hand-written code. By calling the dummy module a different name you
have to make <EM>two</EM> mistakes in a row before you do this. <p>
If you installed Tk support when you installed Python this is extremely
simple. You start modulator and are provided with a form in which you
fill out the details of the module you are creating. <p>
<IMG SRC="html.icons/modulator.gif" ALIGN=CENTER><p>
You'll need to supply a module name (<CODE>interslip</CODE>, in our
case), a module abbreviation (<CODE>pyis</CODE>, which is used as a
prefix to all the routines and data structures modulator will create
for you) and you enter the names of all the methods your module will
export (the list above, with <CODE>is_</CODE> stripped off). Note that
we use <CODE>pyis</CODE> as the prefix instead of the more logical
<CODE>is</CODE>, since the latter would cause our routine names to
collide with those in the API we are interfacing to! The method names
are the names as seen by the python program, and the C routine names
will have the prefix and an underscore prepended. Modulator can do
much more, like generating code for objects and such, but that is a
topic for a later example. <p>
Once you have told modulator all about the module you want to create
you press "check", which checks that you haven't omitted any
information and "Generate code". This will prompt you for a C output
file and generate your module for you. <p>
<H2>Using Modulator without Tk</H2>
Modulator actually uses a two-stage process to create your code: first
the information you provided is turned into a number of python
statements and then these statements are executed to generate your
code. This is done so that you can even use modulator if you don't
have Tk support in Python: you'll just have to write the modulator
python statements by hand (about 10 lines, in our example) and
modulator will generate the C code (about 150 lines, in our
example). Here is the Python code you'll want to execute to generate
our skeleton module: <p>
<CODE><PRE>
import addpack
addpack.addpack('Tools')
addpack.addpack('modulator')
import genmodule
m = genmodule.module()
m.name = 'interslip'
m.abbrev = 'pyis'
m.methodlist = ['open', 'connect', 'disconnect', 'status', \
'getconfig', 'setconfig']
m.objects = []
fp = open('@interslipmodule.c', 'w')
genmodule.write(fp, m)
</PRE></CODE>
Drop this program on the python interpreter and out will come your
skeleton module. <p>
Now, rename the file to interslipmodule.c and you're all set to start
developing. The module is complete in the sense that it should
compile, and that if you import it in a python program you will see
all the methods. It is, of course, not yet complete in a functional
way... <p>
<H2>Creating a plugin module</H2>
The easiest way to build a plugin module is to use the distutils package,
this works fine on MacOS with CodeWarrior. See the distutils documentation
for details. Keep in mind that even though you are on the Mac you specify
filenames with Unix syntax: they are actually URLs, not filenames.
<p>
Alternatively you can build the project file by hand.
Go to the ":Mac:Build" folder and copy the files xx.carbon.mcp,
and xx.carbon.mcp.exp to interslipmodule.carbon.mcp and
interslipmodule.carbon.mcp.exp, respectively. Edit
interslipmodule.carbon.mcp.exp and change the name of the exported routine
"initxx" to "initinterslip". Open interslipmodule.carbon.mcp with CodeWarrior,
remove the file xxmodule.c and add interslipmodule.c and make a number
of adjustments to the preferences:
<UL>
<LI> in PPC target, set the output file name to "interslipmodule.carbon.slb",
<LI> if you are working without a source distribution (i.e. with a normal
binary distribution plus a development distribution) you will not have
a file <code>PythonCoreCarbon</code>. The installation process has deposited this
file in the System <code>Extensions</code> folder under the name
<code>PythonCoreCarbon <i>version</i></code>. Add that file to the project, replacing
<code>PythonCoreCarbon</code>.
<LI> you must either download and build GUSI (if your extension module uses sockets
or other Unix I/O constructs) or remove GUSI references from the Access Paths
settings. See the <a href="building.html">Building</a> document for where to get GUSI
and how to build it.
</UL>
Next, compile and link your module, fire up python and test it. <p>
<H2>Getting the module to do real work</H2>
So far, so good. In half an hour or so we have created a complete new
extension module for Python. The downside, however, is that the module
does not do anything useful. So, in the next half hour we will turn
our beautiful skeleton module into something that is at least as
beautiful but also gets some serious work done. For this once,
<EM>I</EM> have spent that half hour for you, and you can see the
results in <A
HREF="interslip/interslipmodule.c">interslipmodule.c</A>. <p>
We add
<CODE><PRE>
#include "InterslipLib.h"
#include "macglue.h"
</PRE></CODE>
to the top of the file, and work our way through each of the methods
to add the functionality needed. Starting with open, we fill in the
template docstring, the value accessible from Python by looking at
<CODE>interslip.open.__doc__</CODE>. There are not many tools using
this information at the moment, but as soon as class browsers for
python become available having this minimal documentation available is
a good idea. We put "Load the interslip driver" as the comment
here. <p>
Next, we tackle the body of <CODE>pyis_open()</CODE>. Since it has no
arguments and no return value we don't need to mess with that, we just
have to add a call to <CODE>is_open()</CODE> and check the return for
an error code, in which case we raise an error:
<CODE><PRE>
err = is_open();
if ( err ) {
PyErr_Mac(ErrorObject, err);
return NULL;
}
</PRE></CODE>
The routine <CODE><A NAME="PyErr_Mac">PyErr_Mac()</A></CODE> is a
useful routine that raises the exception passed as its first
argument. The data passed with the exception is based on the standard
MacOS error code given, and PyErr_Mac() attempts to locate a textual
description of the error code (which sure beats the "error -14021"
messages that so many macintosh applications tell their poor
users). <p>
We will skip pyis_connect and pyis_disconnect here, which are pretty
much identical to pyis_open: no arguments, no return value, just a
call and an error check. With pyis_status() things get interesting
again: this call still takes 3 arguments, and all happen to be values
returned (a numeric connection status indicator, a message sequence
number and a pointer to the message itself, in MacOS pascal-style
string form). We declare variables to receive the returned values, do
the call, check the error and format the return value. <p>
Building the return value is done using <CODE><A
NAME="Py_BuildValue">Py_BuildValue</A></CODE>:
<CODE><PRE>
return Py_BuildValue("iiO&", (int)status, (int)seqnum, PyMac_BuildStr255, message);
</PRE></CODE>
Py_BuildValue() is a very handy routine that builds tuples according
to a format string, somewhat similar to the way <CODE>printf()</CODE>
works. The format string specifies the arguments expected after the
string, and turns them from C objects into python objects. The
resulting objects are put in a python tuple object and returned. The
"i" format specifier signifies an "int" (hence the cast: status and
seqnum are declared as "long", which is what the is_status() routine
wants, and even though we use a 4-byte project there is really no
reason not to put the cast here). Py_BuildValue and its counterpart
Py_ParseTuple have format codes for all the common C types like ints,
shorts, C-strings, floats, etc. Also, there is a nifty escape
mechanism to format values about which is does not know. This is
invoked by the "O&" format: it expects two arguments, a routine
pointer and an int-sized data object. The routine is called with the
object as a parameter and it should return a python objects
representing the data. <CODE>Macglue.h</CODE> declares a number of
such formatting routines for common MacOS objects like Str255, FSSpec,
OSType, Rect, etc. See the comments in the include file for
details. <p>
<CODE>Pyis_getconfig()</CODE> is again similar to pyis_getstatus, only
two minor points are worth noting here. First, the C API return the
input and output baudrate squashed together into a single 4-byte
long. We separate them out before returning the result to
python. Second, whereas the status call returned us a pointer to a
<CODE>Str255</CODE> it kept we are responsible for allocating the
<CODE>Str255</CODE> for getconfig. This is something that would have
been easy to get wrong had we not used prototypes everywhere. Morale:
always try to include the header files for interfaces to libraries and
other stuff, so that the compiler can catch any mistakes you make. <p>
<CODE>Pyis_setconfig()</CODE> finally shows off
<CODE>Py_ParseTuple</CODE>, the companion function to
<CODE>Py_BuildValue</CODE>. You pass it the argument tuple "args"
that your method gets as its second argument, a format string and
pointers to where you want the arguments stored. Again, standard C
types such as strings and integers Py_ParseTuple knows all about and
through the "O&" format you can extend the functionality. For each
"O&" you pass a function pointer and a pointer to a data area. The
function will be called with a PyObject pointer and your data pointer
and it should convert the python object to the correct C type. It
should return 1 on success and 0 on failure. Again, a number of
converters for standard MacOS types are provided, and declared in
<CODE>macglue.h</CODE>. <p>
Next in our source file comes the method table for our module, which
has been generated by modulator (and it did a good job too!), but
which is worth looking at for a moment. Entries are of the form
<CODE><PRE>
{"open", pyis_open, 1, pyis_open__doc__},
</PRE></CODE>
where the entries are python method name, C routine pointer, flags and
docstring pointer. The value to note is the 1 for the flags: this
signifies that you want to use "new-style" Py_ParseTuple behaviour. If
you are writing a new module always use this, but if you are modifying
old code which calls something like <CODE>getargs(args, "(ii)",
...)</CODE> you will have to put zero here. See "extending and
embedding" or possibly the getargs.c source file for details if you
need them. <p>
Finally, we add some code to the init module, to put some symbolic
constants (codes that can by returned by the status method) in the
module dictionary, so the python program can use "interslip.RUN"
instead of the cryptic "4" when it wants to check that the interslip
driver is in RUN state. Modulator has already generated code to get at
the module dictionary using PyModule_GetDict() to store the exception
object, so we simply call
<CODE><PRE>
PyDict_SetItemString(d, "IDLE", PyInt_FromLong(IS_IDLE));
</PRE></CODE>
for each of our items. Since the last bit of code in our init routine
checks for previous errors with <CODE>PyErr_Occurred()</CODE> and
since <CODE>PyDict_SetItemString()</CODE> gracefully handles the case
of <CODE>NULL</CODE> parameters (if <CODE>PyInt_FromLong()</CODE>
failed, for instance) we don't have to do error checking here. In some
other cases you may have to do error checking yourself. <p>
This concludes our crash-course on writing Python extensions in C on
the Macintosh. If you are not done reading yet I suggest you look
back at the <A HREF="index.html">MacPython Crashcourse index</A> to
find another topic to study. <p>