Dynamically loadable modules (and Doc/ext.tex) work BEAUTIFULLY!

Bennett Todd (bet@std.sbi.com)
Fri, 14 Jan 1994 00:50:44 -0500 (EST)

I've got this fantasy that I'll find a small, beautiful, clean language that
can be easily, efficiently extended with clean hooks into all kinds of
support libraries. It looks like Python is the winner to date.

As an experiment, I'm trying to teach Python to talk to YP. I'm running on a
sun4c Sunos 4.1.1, with gcc 2.5.5.

Just using the ``Extending and Embedding'' doc from 1.0.0beta, and
xxmodule.c for a template, plus a little grepping through Include, I quickly
coded up the start of the C glue to get at the YP client library ypclnt(3n).
After fixing typos until the code passwd ``gcc -Wall'', the resulting
dynamically-loadable library worked perfectly the first time. Kudos for the
superb documentation! I'm particulary impressed with the beauty of the docs
and code, since it has been over 2 years since I last programmed in C.

I only found one little ambiguous bit: how should the C procedures lay their
hands on the error object for their module? Is there a procedure they should
use to look it up in the dictionary any place they want to raise it, or is
it better to keep a private (static) C pointer to the object, the way I've
done? Also, do I need to be INCREFing my error object any place I use it?
Or is that being done by err_setstr()? I did try testing it by repeatedly
generating YP errors (looking up keys that don't exist) and it didn't hoot
at me, but I'm not sure that's an exhaustive test.

Also, I think I've found something at least misleading if not wrong in
ext.tex. It says, in section 3.4 (Caveats) that a #! script shouldn't be
able to correctly do dynamic loading. Being inclined to try out everything
for myself, I was pleased to find that it does seem to work correctly.

Anyway, now I've got to finish writing ypclntmodule.c (I've only written the
first few routines so far), then start on yp.py, a nicer cleaner Python-style
interface to the low-level functions.

If this works, then for my next trick I'll try to see how many of the
currently built-in modules can be turned into dynamically loaded ones; it
looks like the effort to do so (and the code and build procedure diffs) will
be pleasantly small. See, I want the best of all worlds: I want a clean,
beautiful language with a teensy small interpreter, and I want to be able to
efficiently get at all _kinds_ of higher-level library functionality. We'll
see how it goes....

Here's the Makefile I used to build my dynamically-loadable library:

INCDIR=../python-1.0.0beta/Include

CC=gcc
CFLAGS=-fPIC -O6 -Wall -I$(INCDIR)

MODULE=ypclnt
TARGET=$(MODULE)module

$(TARGET).so: $(TARGET).o
ld -o $@ $<

The INCDIR will need changing, depending on where your Python Include
directory is located; the CC and CFLAGS will need changing if you don't have
gcc. This produces ypclntmodule.so. With that in your current working
directory, or in Python's search path, you can then go (e.g.):

$ python
1.0.0 BETA (Jan 3 1994)
Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
>>> from ypclnt import *
>>> d = get_default_domain()
>>> match(d, 'services.byname', '25/tcp')
'smtp\011\01125/tcp\011\011mail'
>>> ^D
$

And last but not least, after my .sig I append what I've written so far of
ypclntmodule.c.

-Bennett
bet@sbi.com

/* ypclnt module */

/*
* This is trivial C glue defining a module ``ypclnt'' containing primitive
* Python functions that precisely mirror ypclnt(3n).
*
* With this available, a clean, pretty, OO interface to NIS can be written
* (I hope!) in Python. I'll try and hack that out in ``yp.py''.
*/

#include "allobjects.h"
#include "modsupport.h"
#include "errors.h"

/*
* This is mine. I get a pointer to it from newstringobject when I
* initialize, and I occasionally pass it into err_setstr().
*/
static object *errobj;

/*
* These are the routines I'm gluing to. It's a shame SMI didn't provide a
* header file with these declarations.
*/
extern int yp_bind(const char *);
extern char *yperr_string(int);
extern void yp_unbind(const char *);
extern int yp_get_default_domain(char **);
extern int yp_match(const char *, const char *, const char *,
int, char **, int *);

/*
* Here are the wrappers. After the first one has been studied closely,
* there really isn't anything new to be learned from the rest of them. This
* is basically a brainless exercise in smooshing together Doc/ext.tex,
* Modules/xxmodule.c, and ypclnt(3n). But look to the bottom for the init
* routine that plugs all these into the Python interpreter's symbol table.
*/
static object *
ypclnt_bind(self, args)
object *self; /* Not used */
object *args;
{
char *indomain;
int result;

if (!getargs(args, "s", &indomain))
return NULL;
if ((result = yp_bind(indomain))) {
err_setstr(errobj, yperr_string(result));
return NULL;
}
INCREF(None);
return None;
}

static object *
ypclnt_unbind(self, args)
object *self; /* Not used */
object *args;
{
char *indomain;

if (!getargs(args, "s", &indomain))
return NULL;
yp_unbind(indomain);
INCREF(None);
return None;
}

static object *
ypclnt_get_default_domain(self, args)
object *self; /* Not used */
object *args;
{
char *outdomain;
int result;

if ((result = yp_get_default_domain(&outdomain))) {
err_setstr(errobj, yperr_string(result));
return NULL;
}
return(newstringobject(outdomain));
}

static object *
ypclnt_match(self, args)
object *self; /* Not used */
object *args;
{
char *indomain, *inmap, *inkey, *outval;
int inkeylen, outvallen, result;

if (!getargs(args, "(sss#)", &indomain, &inmap, &inkey, &inkeylen))
return(NULL);
if ((result = yp_match(indomain, inmap, inkey, inkeylen, &outval, &outvallen))) {
err_setstr(errobj, yperr_string(result));
return NULL;
}
return(newsizedstringobject(outval, outvallen));
}

/* List of functions defined in the module */

static struct methodlist ypclnt_methods[] = {
{"bind", ypclnt_bind},
{"unbind", ypclnt_unbind},
{"get_default_domain", ypclnt_get_default_domain},
{"match", ypclnt_match},
{NULL, NULL} /* sentinel */
};

/* Initialization function for the module (*must* be called initypclnt) */

void
initypclnt()
{
object *m, *d;

/* Create the module and add the functions */
m = initmodule("ypclnt", ypclnt_methods);

/* Add an error constant to the module */
d = getmoduledict(m);
errobj = newstringobject("ypclnt.error");
dictinsert(d, "error", errobj);

/* Check for errors */
if (err_occurred())
fatal("can't initialize module ypclnt");
}