Initial commit of OpenSPARC T2 architecture model.
[OpenSPARC-T2-SAM] / sam-t2 / devtools / amd64 / html / python / ext / node22.html
CommitLineData
920dae64
AT
1<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
2<html>
3<head>
4<link rel="STYLESHEET" href="ext.css" type='text/css' />
5<link rel="SHORTCUT ICON" href="../icons/pyfav.png" type="image/png" />
6<link rel='start' href='../index.html' title='Python Documentation Index' />
7<link rel="first" href="ext.html" title='Extending and Embedding the Python Interpreter' />
8<link rel='contents' href='contents.html' title="Contents" />
9<link rel='last' href='about.html' title='About this document...' />
10<link rel='help' href='about.html' title='About this document...' />
11<link rel="next" href="node23.html" />
12<link rel="prev" href="dnt-basics.html" />
13<link rel="parent" href="dnt-basics.html" />
14<link rel="next" href="node23.html" />
15<meta name='aesop' content='information' />
16<title>2.1.1 Adding data and methods to the Basic example</title>
17</head>
18<body>
19<DIV CLASS="navigation">
20<div id='top-navigation-panel' xml:id='top-navigation-panel'>
21<table align="center" width="100%" cellpadding="0" cellspacing="2">
22<tr>
23<td class='online-navigation'><a rel="prev" title="2.1 The Basics"
24 href="dnt-basics.html"><img src='../icons/previous.png'
25 border='0' height='32' alt='Previous Page' width='32' /></A></td>
26<td class='online-navigation'><a rel="parent" title="2.1 The Basics"
27 href="dnt-basics.html"><img src='../icons/up.png'
28 border='0' height='32' alt='Up One Level' width='32' /></A></td>
29<td class='online-navigation'><a rel="next" title="2.1.2 Providing finer control"
30 href="node23.html"><img src='../icons/next.png'
31 border='0' height='32' alt='Next Page' width='32' /></A></td>
32<td align="center" width="100%">Extending and Embedding the Python Interpreter</td>
33<td class='online-navigation'><a rel="contents" title="Table of Contents"
34 href="contents.html"><img src='../icons/contents.png'
35 border='0' height='32' alt='Contents' width='32' /></A></td>
36<td class='online-navigation'><img src='../icons/blank.png'
37 border='0' height='32' alt='' width='32' /></td>
38<td class='online-navigation'><img src='../icons/blank.png'
39 border='0' height='32' alt='' width='32' /></td>
40</tr></table>
41<div class='online-navigation'>
42<b class="navlabel">Previous:</b>
43<a class="sectref" rel="prev" href="dnt-basics.html">2.1 The Basics</A>
44<b class="navlabel">Up:</b>
45<a class="sectref" rel="parent" href="dnt-basics.html">2.1 The Basics</A>
46<b class="navlabel">Next:</b>
47<a class="sectref" rel="next" href="node23.html">2.1.2 Providing finer control</A>
48</div>
49<hr /></div>
50</DIV>
51<!--End of Navigation Panel-->
52
53<H2><A NAME="SECTION004110000000000000000">
542.1.1 Adding data and methods to the Basic example</A>
55</H2>
56
57<P>
58Let's expend the basic example to add some data and methods. Let's
59also make the type usable as a base class. We'll create
60a new module, <tt class="module">noddy2</tt> that adds these capabilities:
61
62<P>
63<div class="verbatim">
64<pre>#include &lt;Python.h&gt;
65#include "structmember.h"
66
67typedef struct {
68 PyObject_HEAD
69 PyObject *first; /* first name */
70 PyObject *last; /* last name */
71 int number;
72} Noddy;
73
74static void
75Noddy_dealloc(Noddy* self)
76{
77 Py_XDECREF(self-&gt;first);
78 Py_XDECREF(self-&gt;last);
79 self-&gt;ob_type-&gt;tp_free((PyObject*)self);
80}
81
82static PyObject *
83Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
84{
85 Noddy *self;
86
87 self = (Noddy *)type-&gt;tp_alloc(type, 0);
88 if (self != NULL) {
89 self-&gt;first = PyString_FromString("");
90 if (self-&gt;first == NULL)
91 {
92 Py_DECREF(self);
93 return NULL;
94 }
95
96 self-&gt;last = PyString_FromString("");
97 if (self-&gt;last == NULL)
98 {
99 Py_DECREF(self);
100 return NULL;
101 }
102
103 self-&gt;number = 0;
104 }
105
106 return (PyObject *)self;
107}
108
109static int
110Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
111{
112 PyObject *first=NULL, *last=NULL, *tmp;
113
114 static char *kwlist[] = {"first", "last", "number", NULL};
115
116 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
117 &amp;first, &amp;last,
118 &amp;self-&gt;number))
119 return -1;
120
121 if (first) {
122 tmp = self-&gt;first;
123 Py_INCREF(first);
124 self-&gt;first = first;
125 Py_XDECREF(tmp);
126 }
127
128 if (last) {
129 tmp = self-&gt;last;
130 Py_INCREF(last);
131 self-&gt;last = last;
132 Py_XDECREF(tmp);
133 }
134
135 return 0;
136}
137
138static PyMemberDef Noddy_members[] = {
139 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
140 "first name"},
141 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
142 "last name"},
143 {"number", T_INT, offsetof(Noddy, number), 0,
144 "noddy number"},
145 {NULL} /* Sentinel */
146};
147
148static PyObject *
149Noddy_name(Noddy* self)
150{
151 static PyObject *format = NULL;
152 PyObject *args, *result;
153
154 if (format == NULL) {
155 format = PyString_FromString("%s %s");
156 if (format == NULL)
157 return NULL;
158 }
159
160 if (self-&gt;first == NULL) {
161 PyErr_SetString(PyExc_AttributeError, "first");
162 return NULL;
163 }
164
165 if (self-&gt;last == NULL) {
166 PyErr_SetString(PyExc_AttributeError, "last");
167 return NULL;
168 }
169
170 args = Py_BuildValue("OO", self-&gt;first, self-&gt;last);
171 if (args == NULL)
172 return NULL;
173
174 result = PyString_Format(format, args);
175 Py_DECREF(args);
176
177 return result;
178}
179
180static PyMethodDef Noddy_methods[] = {
181 {"name", (PyCFunction)Noddy_name, METH_NOARGS,
182 "Return the name, combining the first and last name"
183 },
184 {NULL} /* Sentinel */
185};
186
187static PyTypeObject NoddyType = {
188 PyObject_HEAD_INIT(NULL)
189 0, /*ob_size*/
190 "noddy.Noddy", /*tp_name*/
191 sizeof(Noddy), /*tp_basicsize*/
192 0, /*tp_itemsize*/
193 (destructor)Noddy_dealloc, /*tp_dealloc*/
194 0, /*tp_print*/
195 0, /*tp_getattr*/
196 0, /*tp_setattr*/
197 0, /*tp_compare*/
198 0, /*tp_repr*/
199 0, /*tp_as_number*/
200 0, /*tp_as_sequence*/
201 0, /*tp_as_mapping*/
202 0, /*tp_hash */
203 0, /*tp_call*/
204 0, /*tp_str*/
205 0, /*tp_getattro*/
206 0, /*tp_setattro*/
207 0, /*tp_as_buffer*/
208 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
209 "Noddy objects", /* tp_doc */
210 0, /* tp_traverse */
211 0, /* tp_clear */
212 0, /* tp_richcompare */
213 0, /* tp_weaklistoffset */
214 0, /* tp_iter */
215 0, /* tp_iternext */
216 Noddy_methods, /* tp_methods */
217 Noddy_members, /* tp_members */
218 0, /* tp_getset */
219 0, /* tp_base */
220 0, /* tp_dict */
221 0, /* tp_descr_get */
222 0, /* tp_descr_set */
223 0, /* tp_dictoffset */
224 (initproc)Noddy_init, /* tp_init */
225 0, /* tp_alloc */
226 Noddy_new, /* tp_new */
227};
228
229static PyMethodDef module_methods[] = {
230 {NULL} /* Sentinel */
231};
232
233#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
234#define PyMODINIT_FUNC void
235#endif
236PyMODINIT_FUNC
237initnoddy2(void)
238{
239 PyObject* m;
240
241 if (PyType_Ready(&amp;NoddyType) &lt; 0)
242 return;
243
244 m = Py_InitModule3("noddy2", module_methods,
245 "Example module that creates an extension type.");
246
247 if (m == NULL)
248 return;
249
250 Py_INCREF(&amp;NoddyType);
251 PyModule_AddObject(m, "Noddy", (PyObject *)&amp;NoddyType);
252}
253</pre>
254<div class="footer">
255<a href="noddy2.txt" type="text/plain">Download as text (original file name: <span class="file">noddy2.c</span>).</a>
256</div></div>
257
258<P>
259This version of the module has a number of changes.
260
261<P>
262We've added an extra include:
263
264<P>
265<div class="verbatim"><pre>
266#include "structmember.h"
267</pre></div>
268
269<P>
270This include provides declarations that we use to handle attributes,
271as described a bit later.
272
273<P>
274The name of the <tt class="class">Noddy</tt> object structure has been shortened to
275<tt class="class">Noddy</tt>. The type object name has been shortened to
276<tt class="class">NoddyType</tt>.
277
278<P>
279The <tt class="class">Noddy</tt> type now has three data attributes, <var>first</var>,
280<var>last</var>, and <var>number</var>. The <var>first</var> and <var>last</var>
281variables are Python strings containing first and last names. The
282<var>number</var> attribute is an integer.
283
284<P>
285The object structure is updated accordingly:
286
287<P>
288<div class="verbatim"><pre>
289typedef struct {
290 PyObject_HEAD
291 PyObject *first;
292 PyObject *last;
293 int number;
294} Noddy;
295</pre></div>
296
297<P>
298Because we now have data to manage, we have to be more careful about
299object allocation and deallocation. At a minimum, we need a
300deallocation method:
301
302<P>
303<div class="verbatim"><pre>
304static void
305Noddy_dealloc(Noddy* self)
306{
307 Py_XDECREF(self-&gt;first);
308 Py_XDECREF(self-&gt;last);
309 self-&gt;ob_type-&gt;tp_free((PyObject*)self);
310}
311</pre></div>
312
313<P>
314which is assigned to the <tt class="member">tp_dealloc</tt> member:
315
316<P>
317<div class="verbatim"><pre>
318 (destructor)Noddy_dealloc, /*tp_dealloc*/
319</pre></div>
320
321<P>
322This method decrements the reference counts of the two Python
323attributes. We use <tt class="cfunction">Py_XDECREF()</tt> here because the
324<tt class="member">first</tt> and <tt class="member">last</tt> members could be <tt class="constant">NULL</tt>. It then
325calls the <tt class="member">tp_free</tt> member of the object's type to free the
326object's memory. Note that the object's type might not be
327<tt class="class">NoddyType</tt>, because the object may be an instance of a
328subclass.
329
330<P>
331We want to make sure that the first and last names are initialized to
332empty strings, so we provide a new method:
333
334<P>
335<div class="verbatim"><pre>
336static PyObject *
337Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
338{
339 Noddy *self;
340
341 self = (Noddy *)type-&gt;tp_alloc(type, 0);
342 if (self != NULL) {
343 self-&gt;first = PyString_FromString("");
344 if (self-&gt;first == NULL)
345 {
346 Py_DECREF(self);
347 return NULL;
348 }
349
350 self-&gt;last = PyString_FromString("");
351 if (self-&gt;last == NULL)
352 {
353 Py_DECREF(self);
354 return NULL;
355 }
356
357 self-&gt;number = 0;
358 }
359
360 return (PyObject *)self;
361}
362</pre></div>
363
364<P>
365and install it in the <tt class="member">tp_new</tt> member:
366
367<P>
368<div class="verbatim"><pre>
369 Noddy_new, /* tp_new */
370</pre></div>
371
372<P>
373The new member is responsible for creating (as opposed to
374initializing) objects of the type. It is exposed in Python as the
375<tt class="method">__new__()</tt> method. See the paper titled ``Unifying types and
376classes in Python'' for a detailed discussion of the <tt class="method">__new__()</tt>
377method. One reason to implement a new method is to assure the initial
378values of instance variables. In this case, we use the new method to
379make sure that the initial values of the members <tt class="member">first</tt> and
380<tt class="member">last</tt> are not <tt class="constant">NULL</tt>. If we didn't care whether the initial
381values were <tt class="constant">NULL</tt>, we could have used <tt class="cfunction">PyType_GenericNew()</tt> as
382our new method, as we did before. <tt class="cfunction">PyType_GenericNew()</tt>
383initializes all of the instance variable members to <tt class="constant">NULL</tt>.
384
385<P>
386The new method is a static method that is passed the type being
387instantiated and any arguments passed when the type was called,
388and that returns the new object created. New methods always accept
389positional and keyword arguments, but they often ignore the arguments,
390leaving the argument handling to initializer methods. Note that if the
391type supports subclassing, the type passed may not be the type being
392defined. The new method calls the tp_alloc slot to allocate memory.
393We don't fill the <tt class="member">tp_alloc</tt> slot ourselves. Rather
394<tt class="cfunction">PyType_Ready()</tt> fills it for us by inheriting it from our
395base class, which is <tt class="class">object</tt> by default. Most types use the
396default allocation.
397
398<P>
399<span class="note"><b class="label">Note:</b>
400If you are creating a co-operative <tt class="member">tp_new</tt> (one that
401calls a base type's <tt class="member">tp_new</tt> or <tt class="method">__new__</tt>), you
402must <em>not</em> try to determine what method to call using
403method resolution order at runtime. Always statically determine
404what type you are going to call, and call its <tt class="member">tp_new</tt>
405directly, or via <code>type-&gt;tp_base-&gt;tp_new</code>. If you do
406not do this, Python subclasses of your type that also inherit
407from other Python-defined classes may not work correctly.
408(Specifically, you may not be able to create instances of
409such subclasses without getting a <tt class="exception">TypeError</tt>.)</span>
410
411<P>
412We provide an initialization function:
413
414<P>
415<div class="verbatim"><pre>
416static int
417Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
418{
419 PyObject *first=NULL, *last=NULL, *tmp;
420
421 static char *kwlist[] = {"first", "last", "number", NULL};
422
423 if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
424 &amp;first, &amp;last,
425 &amp;self-&gt;number))
426 return -1;
427
428 if (first) {
429 tmp = self-&gt;first;
430 Py_INCREF(first);
431 self-&gt;first = first;
432 Py_XDECREF(tmp);
433 }
434
435 if (last) {
436 tmp = self-&gt;last;
437 Py_INCREF(last);
438 self-&gt;last = last;
439 Py_XDECREF(tmp);
440 }
441
442 return 0;
443}
444</pre></div>
445
446<P>
447by filling the <tt class="member">tp_init</tt> slot.
448
449<P>
450<div class="verbatim"><pre>
451 (initproc)Noddy_init, /* tp_init */
452</pre></div>
453
454<P>
455The <tt class="member">tp_init</tt> slot is exposed in Python as the
456<tt class="method">__init__()</tt> method. It is used to initialize an object after
457it's created. Unlike the new method, we can't guarantee that the
458initializer is called. The initializer isn't called when unpickling
459objects and it can be overridden. Our initializer accepts arguments
460to provide initial values for our instance. Initializers always accept
461positional and keyword arguments.
462
463<P>
464Initializers can be called multiple times. Anyone can call the
465<tt class="method">__init__()</tt> method on our objects. For this reason, we have
466to be extra careful when assigning the new values. We might be
467tempted, for example to assign the <tt class="member">first</tt> member like this:
468
469<P>
470<div class="verbatim"><pre>
471 if (first) {
472 Py_XDECREF(self-&gt;first);
473 Py_INCREF(first);
474 self-&gt;first = first;
475 }
476</pre></div>
477
478<P>
479But this would be risky. Our type doesn't restrict the type of the
480<tt class="member">first</tt> member, so it could be any kind of object. It could
481have a destructor that causes code to be executed that tries to
482access the <tt class="member">first</tt> member. To be paranoid and protect
483ourselves against this possibility, we almost always reassign members
484before decrementing their reference counts. When don't we have to do
485this?
486
487<UL>
488<LI>when we absolutely know that the reference count is greater than
489 1
490</LI>
491<LI>when we know that deallocation of the object<A NAME="tex2html5"
492 HREF="#foot1098"><SUP>2.1</SUP></A> will not cause any
493 calls back into our type's code
494</LI>
495<LI>when decrementing a reference count in a <tt class="member">tp_dealloc</tt>
496 handler when garbage-collections is not supported<A NAME="tex2html6"
497 HREF="#foot1418"><SUP>2.2</SUP></A>
498</LI>
499<LI>
500</LI>
501</UL>
502
503<P>
504We want to want to expose our instance variables as attributes. There
505are a number of ways to do that. The simplest way is to define member
506definitions:
507
508<P>
509<div class="verbatim"><pre>
510static PyMemberDef Noddy_members[] = {
511 {"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
512 "first name"},
513 {"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
514 "last name"},
515 {"number", T_INT, offsetof(Noddy, number), 0,
516 "noddy number"},
517 {NULL} /* Sentinel */
518};
519</pre></div>
520
521<P>
522and put the definitions in the <tt class="member">tp_members</tt> slot:
523
524<P>
525<div class="verbatim"><pre>
526 Noddy_members, /* tp_members */
527</pre></div>
528
529<P>
530Each member definition has a member name, type, offset, access flags
531and documentation string. See the ``Generic Attribute Management''
532section below for details.
533
534<P>
535A disadvantage of this approach is that it doesn't provide a way to
536restrict the types of objects that can be assigned to the Python
537attributes. We expect the first and last names to be strings, but any
538Python objects can be assigned. Further, the attributes can be
539deleted, setting the C pointers to <tt class="constant">NULL</tt>. Even though we can make
540sure the members are initialized to non-<tt class="constant">NULL</tt> values, the members can
541be set to <tt class="constant">NULL</tt> if the attributes are deleted.
542
543<P>
544We define a single method, <tt class="method">name</tt>, that outputs the objects
545name as the concatenation of the first and last names.
546
547<P>
548<div class="verbatim"><pre>
549static PyObject *
550Noddy_name(Noddy* self)
551{
552 static PyObject *format = NULL;
553 PyObject *args, *result;
554
555 if (format == NULL) {
556 format = PyString_FromString("%s %s");
557 if (format == NULL)
558 return NULL;
559 }
560
561 if (self-&gt;first == NULL) {
562 PyErr_SetString(PyExc_AttributeError, "first");
563 return NULL;
564 }
565
566 if (self-&gt;last == NULL) {
567 PyErr_SetString(PyExc_AttributeError, "last");
568 return NULL;
569 }
570
571 args = Py_BuildValue("OO", self-&gt;first, self-&gt;last);
572 if (args == NULL)
573 return NULL;
574
575 result = PyString_Format(format, args);
576 Py_DECREF(args);
577
578 return result;
579}
580</pre></div>
581
582<P>
583The method is implemented as a C function that takes a <tt class="class">Noddy</tt> (or
584<tt class="class">Noddy</tt> subclass) instance as the first argument. Methods
585always take an instance as the first argument. Methods often take
586positional and keyword arguments as well, but in this cased we don't
587take any and don't need to accept a positional argument tuple or
588keyword argument dictionary. This method is equivalent to the Python
589method:
590
591<P>
592<div class="verbatim"><pre>
593 def name(self):
594 return "%s %s" % (self.first, self.last)
595</pre></div>
596
597<P>
598Note that we have to check for the possibility that our <tt class="member">first</tt>
599and <tt class="member">last</tt> members are <tt class="constant">NULL</tt>. This is because they can be
600deleted, in which case they are set to <tt class="constant">NULL</tt>. It would be better to
601prevent deletion of these attributes and to restrict the attribute
602values to be strings. We'll see how to do that in the next section.
603
604<P>
605Now that we've defined the method, we need to create an array of
606method definitions:
607
608<P>
609<div class="verbatim"><pre>
610static PyMethodDef Noddy_methods[] = {
611 {"name", (PyCFunction)Noddy_name, METH_NOARGS,
612 "Return the name, combining the first and last name"
613 },
614 {NULL} /* Sentinel */
615};
616</pre></div>
617
618<P>
619and assign them to the <tt class="member">tp_methods</tt> slot:
620
621<P>
622<div class="verbatim"><pre>
623 Noddy_methods, /* tp_methods */
624</pre></div>
625
626<P>
627Note that we used the <tt class="constant">METH_NOARGS</tt> flag to indicate that the
628method is passed no arguments.
629
630<P>
631Finally, we'll make our type usable as a base class. We've written
632our methods carefully so far so that they don't make any assumptions
633about the type of the object being created or used, so all we need to
634do is to add the <tt class="constant">Py_TPFLAGS_BASETYPE</tt> to our class flag
635definition:
636
637<P>
638<div class="verbatim"><pre>
639 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
640</pre></div>
641
642<P>
643We rename <tt class="cfunction">initnoddy()</tt> to <tt class="cfunction">initnoddy2()</tt>
644and update the module name passed to <tt class="cfunction">Py_InitModule3()</tt>.
645
646<P>
647Finally, we update our <span class="file">setup.py</span> file to build the new module:
648
649<P>
650<div class="verbatim"><pre>
651from distutils.core import setup, Extension
652setup(name="noddy", version="1.0",
653 ext_modules=[
654 Extension("noddy", ["noddy.c"]),
655 Extension("noddy2", ["noddy2.c"]),
656 ])
657</pre></div>
658
659<P>
660<BR><HR><H4>Footnotes</H4>
661<DL>
662<DT><A NAME="foot1098">... object</A><A
663 HREF="node22.html#tex2html5"><SUP>2.1</SUP></A></DT>
664<DD>This is
665 true when we know that the object is a basic type, like a string or
666 a float
667
668</DD>
669<DT><A NAME="foot1418">... supported</A><A
670 HREF="node22.html#tex2html6"><SUP>2.2</SUP></A></DT>
671<DD>We relied
672 on this in the <tt class="member">tp_dealloc</tt> handler in this example, because
673 our type doesn't support garbage collection. Even if a type supports
674 garbage collection, there are calls that can be made to ``untrack''
675 the object from garbage collection, however, these calls are
676 advanced and not covered here.
677
678</DD>
679</DL>
680<DIV CLASS="navigation">
681<div class='online-navigation'>
682<p></p><hr />
683<table align="center" width="100%" cellpadding="0" cellspacing="2">
684<tr>
685<td class='online-navigation'><a rel="prev" title="2.1 The Basics"
686 href="dnt-basics.html"><img src='../icons/previous.png'
687 border='0' height='32' alt='Previous Page' width='32' /></A></td>
688<td class='online-navigation'><a rel="parent" title="2.1 The Basics"
689 href="dnt-basics.html"><img src='../icons/up.png'
690 border='0' height='32' alt='Up One Level' width='32' /></A></td>
691<td class='online-navigation'><a rel="next" title="2.1.2 Providing finer control"
692 href="node23.html"><img src='../icons/next.png'
693 border='0' height='32' alt='Next Page' width='32' /></A></td>
694<td align="center" width="100%">Extending and Embedding the Python Interpreter</td>
695<td class='online-navigation'><a rel="contents" title="Table of Contents"
696 href="contents.html"><img src='../icons/contents.png'
697 border='0' height='32' alt='Contents' width='32' /></A></td>
698<td class='online-navigation'><img src='../icons/blank.png'
699 border='0' height='32' alt='' width='32' /></td>
700<td class='online-navigation'><img src='../icons/blank.png'
701 border='0' height='32' alt='' width='32' /></td>
702</tr></table>
703<div class='online-navigation'>
704<b class="navlabel">Previous:</b>
705<a class="sectref" rel="prev" href="dnt-basics.html">2.1 The Basics</A>
706<b class="navlabel">Up:</b>
707<a class="sectref" rel="parent" href="dnt-basics.html">2.1 The Basics</A>
708<b class="navlabel">Next:</b>
709<a class="sectref" rel="next" href="node23.html">2.1.2 Providing finer control</A>
710</div>
711</div>
712<hr />
713<span class="release-info">Release 2.4.2, documentation updated on 28 September 2005.</span>
714</DIV>
715<!--End of Navigation Panel-->
716<ADDRESS>
717See <i><a href="about.html">About this document...</a></i> for information on suggesting changes.
718</ADDRESS>
719</BODY>
720</HTML>