XS - Einführung und esoterische Anwendungen [Vortrag]

(c) 2000 Marc Lehmann <pcg@goof.com>
1 April 2000


Table of Contents


Abstract

XS, die C/C++-Schnittstelle von Perl ist einfach in der Benutzung und dennoch sehr mächtig. Der erste Teil dieses Vortrags stellt Schritt für Schritt die Erstellung einer Schnittstelle zum Uniforum-Katalog-Standard ("gettext").

Der zweite Teil stellt spezielle Probleme vor, die recht häufig vorkommen und gibt Lösungsvorschläge, sowie einige Tips & Tricks. Das Ziel dieses Teiles ist es, aufzuzeigen, welche Lösungen existieren, und wo man nachschauen muß, falls man auf ein Problem stößt, das schon von anderen gelöst wurde...


1. XS - Wozu, wenn's doch auch system gibt!

Ehrlich gesagt, mir kam diese Frage (oder auch eine ähnliche) nie in den Sinn. Tatsächlich aber ist sie durchaus angebracht: Perl wurde als Klebstoff entwickelt, um bestehende Programme miteinander verbinden zu können (naja, zumindest kann Perl das sehr gut, ob es speziell dafür entwickelt wurde...).

Man kann die meisten Probleme durchaus ohne XS-Programmierung lösen, ja, im Idealfall hat sich schon jemand anderer die Arbeit gemacht, und man muß nur ein paar Zeilen in der CPAN-Shell (tolles Tool!) eingeben, um es zu installieren (plus vielleicht ein zwei Stunden Anpassungen an sein System ;)

Ich sehe zwei Hauptgründe, für die XS-Programmierung:

Mit XS kann man einen Vorgang (z.B. dekodieren von Binaries in Mail oder News) in viele kleine Arbeitsschritte zerlegen. Das erlaubt große Flexibilität. auf der Programmiererseite: Nichts ist schlimmer als ein Programm, daß im Prinzip funktioniert, sich aber nicht genügend fernsteuern läßt.

Darüberhinaus sind XS-Funktionen so weit in Perl integriert, daß sie sich wie eingebaute Funktionen verhalten. So kann man sich eine "Sprache" für ein spezielles Problem maßschneidern. Das ist eleganter und schöner als irgendwelche Hacks mit externen Programmen, die zu einem Pflege-Horror werden.

Außerdem gibt es Dinge, die man einfach nicht mit externen Programmen tun kann - GUI-Toolkits programmieren zum Beispiel. Klar, es geht schon - wenn man sich durch das Labyrinth einer unangepaßten API wühlen möchte...

1.1. übersicht über die Möglichkeiten mit XS

Mit XS kann man grundsätzlich alles machen, was man auf der Perl-Ebene auch kann: Skalare, Hashes, Arrays... Perl-Objekte, Überladen (mehr fällt mir im Moment nicht ein). Wenn etwas fehlt, kein Problem, man kann immer Perl selbst aufrufen.

Darüberhinaus hat man vollen Zugriff auf die Sprache C (bzw. C++) und alle Bibliotheken, die dafür verfügbar sind (eine Menge). Es ist praktisch die einzige Möglichkeit, auf Bibliotheken in anderen Sprachen zurückzugreifen.

Ein häufig vorkommendes Problem ist beispielsweise die Schnittstelle zu einer Bibliothek, die einen internen Zustand speichert, z.B. Compress::Zlib oder Digest::MD5. Je nach Aufwand, den man investieren will und der Flexibilität, die man benötigt, bieten sich drei Wege an:


2. Integration von gettext in Perl

gettext ist eine weitverbreitete Methode zur Lokalisierung von Texten in Programmen. In C ist die Anwendung denkbar einfach. Statt:

printf ("The output is %d%s\n", weight,
        metric ? "kg" : "st");

Schreibt man einfach:

printf (gettext("The output is %d%s\n"), weight,
        metric ? gettext("kg") : gettext("st"));

d.h. man ruft bei jeder String-Ausgabe die Funktion gettext auf und verwendet den Rückgabewert. Je nach ausgewählter (Ziel-) Sprache gibt gettext entweder den String selbst zurück (wenn er nicht übersetzt werden konnte) oder eine entsprechende Übersetzte Variante.

Vor der Benutzung von gettext muß das ganze System noch initialisiert werden, damit das System weiß, welchen Katalog es benutzen soll und wo die Tabellen gespeichert sind:

setlocale (LC_ALL, "");
textdomain ("meine-domain");
bindtextdomain ("meine-domain", "/usr/local/share/locale");

Das Beispiel läßt sich übrigens noch schöner schreiben, wenn man ein:

#define _(s) gettext(s)

verwendet. _ ist ein ungewöhnlicher Name, aber für diesen Zwecke sehr gut geeignet, und vor allem schon "Standard":

printf (_("The output is %d%s\n"), weight,
        metric ? _("kg") : _("st"));

Nun gibt es schon ein (relativ spartanisches) Perl-Modul Locale::gettext, das ein Interface für gettext zur Verfügung stellt. Gäbe es dieses Modul jedoch nicht, wäre man praktisch aufgeschmissen: gettext kommt mit vielen Programmen daher, die das Management von "Katalogen" (Übersetzungstabellen) vereinfachen. Würde man sich eine "nur-perl" Lösung einfallen lassen, könnte man diese vorhandenen Hilfen nicht benutzen.

Ich weiß nicht, wie das Locale::gettext-Modul entstand. Aber ich weiß, das es kein großer Aufwand ist, wenn man die vorhandenen Hilfen in Perl ausschöpft.

2.1. Ein CPAN Modul besteht aus...?

Wenn man ein Modul von CPAN holt, wird einem auffallen, daß die meisten Module einem recht strengen Standard folgen: Es gibt eigentlich immer die Dateien Makefile.PL, ein MANIFEST, ein Changes ein Test-Skript und eine Perl-Moduldatei (Endung .pm) und im Falle von XS-Modulen auch eine Datei *.xs.

Der Grund ist, daß man seine Module nicht selbst "von Grund auf" schreibt, sondern als ersten Schritt das Programm h2xs ("header-nach-xs") verwendet: Als Eingabe nimmt es eine C-Header-Datei und erzeugt ein komplettes Verzeichnis mit allen notwendigen Dateien - schon installationsfähig.

Das Headerfile ist in meinem Falle /usr/include/libintl.h. Es enthält (fast) alle Funktionsdefinitionen, auf die es ankommt. Probieren wir's (das Modul C::Scan muss unbedingt installiert sein!):

cerebro:~/src# h2xs -x -A -n Locale::gettext /usr/include/libintl.h
Scanning typemaps...
 Scanning /usr/app/lib/perl5/ExtUtils/typemap
Scanning /usr/include/libintl.h for functions...
Writing Locale/gettext/gettext.pm
Writing Locale/gettext/gettext.xs
Writing Locale/gettext/Makefile.PL
Writing Locale/gettext/test.pl
Writing Locale/gettext/Changes
Writing Locale/gettext/MANIFEST

Falls es hier Probleme gibt, ist meistens C::Scan dafür verantwortlich. Es ist bei der Installation etwas bockig (make test läuft oft nicht durch) und erkennt viele Konstrukte nicht: Wenn es nicht richtig klappt (so furchtbare Dinge wie C__const> oder auch const verwirren C::Scan leider sehr), sollte man den Header kopieren und ein bißchen editieren (oder umbenennen, da h2xs nur mit .h-Dateien funktioniert).

Je nachdem, wie "verständlich" das Headerfile geschrieben war, erzeugt h2xs (mit C::Scan) für einige oder alle Funktionen eine Perl-Schnittstelle.

2.1.1. Woran erkennt man, ob es geklappt hat?

Schauen wir die neuen Dateien einmal an: Changes ist sehr einfach:

Revision history for Perl extension Locale::gettext.

0.01  Sat Feb  5 22:26:58 2000
        - original version; created by h2xs 1.19

Auch MANIFEST hat nicht viel zu bieten:

Changes
MANIFEST
Makefile.PL
gettext.pm
gettext.xs
test.pl

Was in Ordnung ist: MANIFEST enthält nur eine Liste der Dateien, die Teil des Paketes sind. Dadurch kann bei der Installation festgestellt werden, ob Dateien fehlen. Vor allem aber kann man einfach make dist verwenden, um sein Perl-Modul in eine versendbare Form zu bringen.

test.pl ist ein einfaches Test-Skript, das bei einem make test ausgeführt wird und sicherstellt, daß das Erweiterungsmodul geladen wird.

Das erzeuge Makefile.PL enthält auch nicht viel, daß angepaßt werden müßte:

use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
    'NAME'   => 'Locale::gettext',
    'VERSION_FROM' => 'gettext.pm', # finds $VERSION
    'LIBS'   => [''],   # e.g., '-lm'
    'DEFINE' => '',     # e.g., '-DHAVE_SOMETHING'
    'INC'    => '',     # e.g., '-I/usr/include/other'
);

Einzig und allein das Attribut LIBS könnte auf den Wert ['-lintl'] gesetzt werden, da sich die gettext-Funktionen auf vielen Systemen in dieser Bibliothek verbergen.

Nun zu gettext.pm: Hier sollte man hier sofort Hand anlegen, vor allem bei der Dokumentation (das Listing befindet sich im Anhang). In diesem Falle sollten zu @EXPORT außerdem noch einige Funktionen, z.B. textdomain und bindtextdomain, hinzugefügt werden, damit sie bequem importiert werden können.

Perl-Module kann aber jeder... das eigentlich Interessante verbirgt sich in gettext.xs:

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include <libintl.h>


MODULE = Locale::gettext                PACKAGE = Locale::gettext


char *
gettext(msgid)
        const char * msgid

char *
dgettext(domainname, msgid)
        const char * domainname
        const char * msgid

char *
textdomain(domainname)
        const char * domainname

char *
bindtextdomain(domainname, dirname)
        const char * domainname
        const char * dirname

Hmm.. sieht eigentlich nicht viel anders als ein C-Headerfile aus. Normalerweise muß man hier einige kleine Korrekturen vornehmen (Position des Headerfiles, löschen überflüssiger Funktionen etc..).

2.2. Benutzung

Probieren geht über studieren. So, wie das Modul aussieht, könnte es glatt funktionieren... probieren wir mal:

cerebro:~/src/Locale/gettext# perl Makefile.PL
Checking if your kit is complete...
Looks good
Writing Makefile for Locale::gettext
cerebro:~/src/Locale/gettext# make
/usr/bin/perl -I/usr/app/lib/perl5/i686-linux -I/usr/app/lib/perl5
/usr/app/lib/perl5/ExtUtils/xsubpp  -typemap
/usr/app/lib/perl5/ExtUtils/typemap gettext.xs > gettext.xsc && mv
gettext.xsc gettext.c
Error: 'const char *' not in typemap in gettext.xs, line 13
Error: 'const char *' not in typemap in gettext.xs, line 17
Error: 'const char *' not in typemap in gettext.xs, line 18
Error: 'const char *' not in typemap in gettext.xs, line 22
Error: 'const char *' not in typemap in gettext.xs, line 26
Error: 'const char *' not in typemap in gettext.xs, line 27
Please specify prototyping behavior for gettext.xs (see perlxs manual)
make: *** [gettext.c] Error 1

Nun gut. Perl kennt "const char *" nicht. Aber wer braucht das auch? ;) Nach dem Entfernen aller "const"'s aus gettext.xs funktioniert es:

cerebro:~/src/Locale/gettext# make
[viele Zeilen Ausgabe, aber keine Fehlermeldung]
cerebro:~/src/Locale/gettext# make test
PERL_DL_NONLAZY=1 /usr/bin/perl -Iblib/arch -Iblib/lib  \
-I/usr/app/lib/perl5/i686-linux -I/usr/app/lib/perl5 test.pl
1..1
ok 1

So weit, so gut:

cerebro:~/src/Locale/gettext# make install
Installing /usr/app/lib/i686-linux/auto/Locale/gettext/gettext.so
Installing /usr/app/lib/i686-linux/auto/Locale/gettext/gettext.bs
Installing /usr/app/lib/i686-linux/Locale/gettext.pm
Installing /usr/app/man/man3/Locale::gettext.3

Und nun der erste, echte, Test. Auf meinem System gibt es eine textdomain namens libc, die unter anderem die Übersetzung von Fehlermeldungen enthält. Probieren wir aus:

use Locale::gettext;

# initialisierung

textdomain "libc";

print gettext("write incomplete"), "\n";

Das Beispiel geht davon aus, daß gettext und textdomain im @EXPORT-Array eingetragen wurden. Ausprobieren:

$ perl first-test
write incomplete
$ LANG=de perl first-test
Der 'Write' wurde nur unvollständig ausgeführt
$ LANG=es perl first-test
escritura incompleta
$ LANG=sv perl first-test
ofullständing skrivning

Ich finde, die deutsche Übersetzung ist am besten ;) Aber davon abgesehen: bisher musste keine einzige Zeile Perl oder C geschrieben werden (von Schönheitskorrekturen mal abgesehen).

Grundsätzlich funktioniert das Modul aber ... Zeit, für Verbesserunge!

2.2.1. setlocale (I)

Das der erste Versuch klappt, liegt daran, daß Perl setlocale aufruft - zumindest in meiner Version von Perl. Der Standardaufruf (der nicht ohne gewichtige Gründe anders ausfallen sollte) dafür lautet normalerweise:

setlocale(LC_ALL, "");

Das überschreibt aber alle Einstellungen, und die Nutzer unseres Moduls sind aber erst einmal an Texten (LC_MESSAGES) interessiert. Es wäre also sehr sinnvoll, wenn beim ersten Laden der Erweiterung automatisch ein setlocale(LC_MESSAGES, "") ausgeführt würde. Das erreicht man mit der BOOT:-Anweisung, die man irgendwo in den XS-Teil der Datei gettext.xs unterbringt (der erste Teil der besteht aus normalem C, erst nach der MODULE-Anweisung beginnt der eigentliche XS-Teil):

BOOT:
        setlocale (LC_MESSAGES, "");

Dies wird ausgeführt, wenn das Modul geladen wird (genauer, wenn in gettext.pm die Methode bootstrap aufgerufen wird), also normalerweise beim ersten use/require Locale::gettext.

Am Anfang der Datei (bei den include-statements muß natürlich noch der header locale.h "included" werden).

Übrigens reagiert der XS-Parser von Perl sehr allergisch auf (manche) Leerzeilen, z.B. wird

BOOT:
        xxx;

#ifdef LC_MESSAGES
        setlocale(LC_MESSAGES, ...);
#endif

Garantiert nicht richtig geparsed. Um solchen Fällen zu entgehen, sollte man immer einen Block () verwenden, wnen man sich nicht ganz sicher ist:

BOOT:

        xxx;

#ifdef LC_MESSAGES
        setlocale(LC_MESSAGES, ...);
#endif

2.2.2. make test

make test (oder das GNU-Äquivalent make check) bieten neuerdings immer mehr Pakete (in anderen Sprachen) - Perl-Module dagegen bieten so etwas fast alle an, und schon seit langer Zeit. Grund ist auch hier die gute Standardisierung: h2xs legt automatisch ein test.pl an, und der MakeMaker bindest das auch automatich ins Makefile ein:

# Before `make install' is performed this script should be runnable with
# `make test'. After `make install' it should work as `perl test.pl'

######################### We start with some black magic to print on failure.

# Change 1..1 below to 1..last_test_to_print .
# (It may become useful if the test is moved to ./t subdirectory.)

BEGIN  $| = 1; print "1..1\n"; 
END print "not ok 1\n" unless $loaded;
use Locale::gettext;
$loaded = 1;
print "ok 1\n";

######################### End of black magic.

So, wie es dasteht, prüft es lediglich, ob das Modul geladen werden kann. Das an sich ist schon ein guter Test, der die meisten Link-Probleme abfängt. Auch ist die Ausgabe von make test nicht sonderlich schön:

cerebro:~/src/Locale/gettext# make test
PERL_DL_NONLAZY=1 /usr/bin/perl -Iblib/arch ...
1..1
ok 1

Besser ist es, wenn man ein eigenes Verzeichnis für test verwendet:

$ mkdir t
$ mv test.pl t/01_basic.t

(Nach solchen chirurgischen Eingriffen sollte man unbedingt das MANIFEST ändern und das Makefile (mit perl Makefile.PL) neu generieren).

Ruft man nun make test auf, sieht es schon viel hübscher aus:

cerebro:~/src/Locale/gettext# make test
PERL_DL_NONLAZY=1 /usr/bin/perl -Iblib/arch ...
t/01_basic..........ok
All tests successful.
Files=1, Tests=1,  0 wallclock secs ( 0.05 cusr +  0.00 csys =  0.05 CPU)

Hier werden alle Datein im Verzeichnis t, deren Name auf .t endet, der Reihe nach (alphabetisch) aufgerufen. Ihre Ausgabe wird überprüft und neben einer Statistik wird ein beruhigendes "All tests successful." ausgegeben.

Dies ist auch der Grund für die merkwürdige Ausgabe des Test-Skriptes: Zuerst wird ausgegeben, welche Tests ausgeführt werden (hier: 1..1, also nur einer). Jeder Test sollte dann ein "ok Nummer" ausgeben - oder ein not ok.

Einen guten Test für dieses Modul zu schreiben, ist garnicht mal so einfach, da dazu eine Übersetzungstabelle installiert werden müßte, was relativ systemabhängig ist. Ein kleiner Test sollte es aber schon sein (die geänderten Stelle wurden hervorgehoben):

BEGIN  $| = 1; print "1..2\n"; 
END print "not ok 1\n" unless $loaded;
use Locale::gettext;
$loaded = 1;
print "ok 1\n";

print gettext("fantasy test text") eq "fantasy test text" ? "ok 2" : "not ok 2";

Wenn man viele Tests schreibt, sollte man sich unbedingt das Modul Test ansehen. Es hilft bei der Planung und nimmt einem das Zählen der Tests ab.

2.3. Eine neue Funktion: __

Immer gettext zu schreiben wird auf Dauer langweilig. Eine schönere Syntax muß her. Zuerst dachte ich daran, eine Unterstrich zu verwenden: print _"Hello, World!\n", aber das scheiter daran, daß der Unterstrich für Perl einfach "zu magisch" ist.

Der (für mich) nächstliegendere Name waren zwei Unterstriche "__". Da in Perl Klammern nahezu überflüssig sind, ist dieser längere Name immer noch kürzer als sein Äquivalent in C:

C:    print _("Text");
Perl: print __"Text";

Solche "Kleinigkeiten" sind in Perl sehr wichtig...

Aber nun denn, schreiten wir zur Implementation. Im Gegensatz zu gettext müssen wir hier die Funktion selbst implementieren:

char *
__(msgid)
        char *       msgid
        CODE:
        RETVAL = gettext (msgid);
        OUTPUT:
        RETVAL

Hmm.. eine Menge neues Holz. Hinzugekommen sind zwei neue XS-Anweisungen: CODE: (gefolgt von C-Anweisungen> und OUTPUT: (um Rückgabewerte zu definieren). Es ginge auch etwas einfacher (z.B. mit einem define), aber obiges klappt mit jeder Funktion.

Was passiert hier? Zuersteinmal, alles, was hinter CODE: folgt ist im wesentlichen normales C (oder C++), man könnte also auch schreiben:

        CODE:
        printf ("DEBUG: __(\"%s\") ", msgid);
        RETVAL = gettext (msgid);
        printf ("=> \"%s\"\n", RETVAL);

(stdio.h nicht vergessen!) Der einzige Unterschied ist die Variable RETVAL: Sie wird von Perl erzeugt, um das Ergebnis aufzunehmen. Was man in RETVAL ablegt, wird auf der Perl-Ebene als Ergbnis zurückgeliefert.

Damit Perl auch weiß, das diese Variable geschrieben wird, muß man das in einer OUTPUT:-Anweisung festlegen. Meiner Meinung nach ist das überflüssig: Wenn Perl schon weiß, daß es RETVAL bereitstellen muß, könnte das auch automatisch passieren. Aber h2xs und der XS-Compiler haben einige archaische Angewohnheiten, oder anders gesagt: es könnte noch besser sein.

2.3.1. PROTOTYPE:

Ein kleiner kosmetischer Fehler ist in der obigen Version aber noch enthalten. Der Aufruf:

print __"Text1", "\n";

geht in die Hose, da __ beide Argumente an sich zieht, damit nicht zurecht kommt und eine Fehlermeldung ausgibt. Deshalb sollte man für __ (und die anderen funktionen) noch einen Prototyp festlegen:

        PROTOTYPE: $

Das setzt den Prototyp auf "$" (__ nimmt also nur einen Skalar an). Es geht übrigens noch einfacher. Man kann global (ähnlich wie BOOT:) festlegen, daß der XS-Compiler automatisch einen (möglichst passenden) Prototyp generieren soll:

PROTOTYPES: ENABLE

Das ist meistens (aber nicht immer) das Richtige.

Die Manpage zu perlxs enthält noch jede Menge weiterer, lustiger Anweisungen...

2.4. Perl-Datenstrukturen in C

Mit dem bisherigen Rüstzeug kann man schon viel machen. Die Intraktion von Perl und C ist aber irgendwie auf dem C-level, da die Funktionen kein undef, keine Referenzen oder anderen Perl-Spezialitäten kennen.

Greifen wir nun in die unerschöpfliche Trickkiste der an den Haaren herbeigezogenen Beispiele und fischen eine gettext-Variante heraus, die sich verhält wie das Original, es sei denn, man füttert sie mit undef - in diesem Fall soll ein Fehler ausgegeben werden.

gettext($string)        # => Übersetzung
gettext()               # => die "Fehler!"

Dazu muß man auf Perl-Datenstrukturen zugreifen (keine Angst, ist ganz einfach). Die wichtigste ist ein "SV" (Scalar Value oder Skalarwert). Da man in Perl nur Skalare an Funktionen übergeben kann (ja, egal, was man schreibt, ob Hash, Referenz, Array oder Konstante, übergeben wird immer ein oder mehrere Skalare), ist der Typ auf der C-Ebene einfach ein "SV *", ein Zeiger auf einen Skalarwert. Statt char * kann man also einfach SV * schreiben:

char *
gettext(msgid)
           SV * msgid

Auf die Struktur selbst darf man nicht zugreifen, dafür gibt es jede Menge Makros (die in der Manpage zu perlguts beschrieben sind). Die wichtigsten sind:

Funktion Beschreibung
SvOK(SV *) Ist der Skalar überhaupt definiert?
SvIOK(SV *) Ist der Skalar eine gültige Integer-Zahl?
SvNOK(SV *) Ist der Skalar eine gültige Fließkomma-Zahl?
SvPOK(SV *) Ist der Skalar ein gültiger String?
SvIV(SV *) Gib den Skalar als IV (integer-value) zurück
SvNV(SV *) Gib den Skalar als NV (numeric-value) zurück
SvPV(SV *, STRLEN len) Gib den Skalar als String zurück (und schreibe die Länge in len)
SvPV_nolen(SV *) Das gleiche, aber ohne Länge (erst ab Perl 5.005)
SvTRUE(SV *) Behandele den Skalar als Wahrheitswert und gib diesen C-gerecht zurück
SvRV(Sv *) Liefert das Objekt, auf das eine Referenz zeigt
newSViv(IV) Erzeuge Skalar aus einem IV
newSVnv/NV) Erzeuge Skalar aus einem NV
newSVpv(const char *, int) Erzeuge neuen Skalar aus einem String (oder anderen Daten)

Wenn man ein bißchen damit arbeitet, wird man bald feststellen daß alle Datentypen sehr vernünftig benannt sind. Ein SV/NV/IV/AV/HV/PV usw. ist immer ein bestimmter Wert (Value), und die Buchstaben S/N/I/A/H/P usw. in Funktionsnamen deuten immer darauf hin, welche Datentypen die Funktion zurückgibt (oder erwartet):

Datentypen
SV Ein Perl-Skalar
AV Ein Perl-Array
HV Ein Perl-Hash
IV Ein Integer (es gibt auch I16, I32 usw.. für Integer mit einer bestimmten Größe)
UV Ein "unsigned-integer" (ohne Vorzeichen) auch davon gibt es U32 ... Varianten
NV Eine Fließkommazahl (numeric value)
PV Ein "Pointer-Wert", also ganz allgemein eine Datenstruktur, meistens aber ein C-String (in Perl ist es bekanntlich möglich, beliebige Inhalte in Skalaren zu speichern).
RV Eine Referenz

2.4.1. Beispiele

Um einen Fehler auszulösen, falls undef übergeben wird, kann man in der gettext-Funktion einfach schreiben:

        if (!SvOK (msgid))
          croak ("cannot translate undefined values");

        RETVAL = gettext (SvPV_nolen (msgid));

croak verhält sich wie sein Perl-Äquivalent im Modul Carp. Wollen wir auch Referenzen auf Strings zulassen (um ein völlig abgedrehtes Beispiel zu bringen), müßte man schreiben:

        if (SvROK (msgid))
          
            if (SvTYPE (SvRV (msgid)) == SVt_PV)
              msgid = SvRV (msgid);
            else
              croak ("only references to scalar values allowed");
          

Mit SvROK wird geprüft, ob eine Referenz vorliegt, wenn ja, wird mit SvRV das Objekt geholt, auf das die Referenz zeigt, und dessen Typ (den man mit SvTYPE bekommt) bestimmt. Ist er SVt_PV (ein "PV"), haben wir den String zum Übersetzen gefunden, alles andere ist ein Fehler.

Dieses - zugegeben gemeine Beispiel - zeigt, daß das wichtigste immer ein zweites Terminal mit der Manpage zu perlguts ist, in der alle diese Funktionen erklärt werden. Ich mußte für das obige Beispiel zwei Dinge nachschauen: ob SvTYPE auch wirklich SvTYPE heißt, und was der Typ für einen PV ist (nämlich SVt_PV).

Also: Mit Manpage = einfach. Ohne Manpage = Guru/Blöd.

2.4.2. Haben Strings ein Nullbyte am Ende?

Laut Dokumentation darf man sich nicht darauf verlassen (sie drückt sich extrem schwammig aus, wohl, weil es ursprünglich geplant war, Strings ohne Nullbyte zu speichern). In der Praxis funktioniert kaum ein Modul, wenn das nicht der Fall wäre. Deshalb kann man davon ausgehen, das Strings immer mit einem Nullbyte enden, und muß dafür sorgen, daß Strings, die man selbst erzeugt, ebenfalls mit einem Nullbyte enden.

2.4.3. Rückgabewerte

Da auch die Rückgabewerte SV's sind, kann man auch hier die Typenumwandlung selbst in die Hand nehmen:

SV *
gettext(msgid)
           SV * msgid
           CODE:
           ...
           OUTPUT:
           RETVAL

Jetzt kann man das Resultat mit Hilfe der Funktion newSvPV von einem C-String in einen SV umwandeln:

        const char *translated = gettext (SvPV_nolen (msgid));
        RETVAL = newSvPV (translated, 0);
        RETVAL = sv_2mortal (RETVAL);

Böse wie XS ist, lauert hier noch ein kleiner Stolperstein, doch zuerst die Funktion newSvPV: Ihr erstes Argument ist der Zeiger auf den C-String (translated). Das zweite Argument ist die Länge. Übergibt man 0, berechnet Perl diese freundlicherweise für uns.

Als Ergebnis winkt ein ganz normaler SV *. Würden wir diesen direkt zurückgeben, hätten wir aber ein Speicherproblem. Warum das so ist, liegt an der Speicherverwaltung von Perl:

Jedes Objekt hat einen Referenzzähler, also eine Zahl, die angibt, von wie vielen "Benutzern" dieses Objekt gerade benutzt wird. Sinkt dieser auf 0, wird es freigegeben, sonst nicht. Auf der Perl-Ebene wird der Referenzzähler z.B. erhöht, wenn man eine Referenz auf einen Skalar erzeugt, und verringert, wenn diese Referenz gelöscht wird, oder das Programm den Gültigkeitsbereich der Variable verläßt:

    
       my $sv = 5;   # SvREFcnt($sv)  == 1
       my $rv = \$sv;        # SvREFcnt($sv)  == 2
       undef $rv;    # SvREFcnt($sv)  == 1
                       # SvREFcnt($sv)  == 0 => LÖSCHEN

Das Problem ist: wenn ein neuer Skalar erzeugt wird, hat dieser den Referenzzähler 1. Von selbst verringert sich dieser nicht, und selbst darf man ihn auch nicht verringern, sonst würde der Skalar ja sofort gelöscht. Deshalb gibt es das Konzept der "Sterblichkeit", was nichts anderes ist, als ein verzögertes herunterzählen des Referenzzählers.

Wenn ein Skalar als "sterblich" (eine ausgesprochen gute Bezeichnung) markiert wurde (mit sv_2mortal), wird sein Referenzzähler etwas später erniedrigt, günstigerweise dann, wenn der Aufrufer die Möglichkeit hatte, den Wert in einer Variablen zu speichern (und damit eine Referenz zu erzeugen).

2.5. Was fehlt noch zur Profi-Version?

Nun sind die wichtigsten Schritte, um ein XS-Modul zu erzeugen, besprochen worden. Alles, was dasrüber hinaus geht, wird nur "bei Bedarf" benötigt, und dann ist es immer gut, sich eine Manpage (perlxs und perlguts) zu schnappen und dort nach einer Lösung zu suchen.

Wenn man auf ein Problem stößt (z.B. "wie übergibt man beliebig viele Argumente"), das man nicht aus der Dokumentation lösen kann, dann ist es sehr hilfreich, eine Weile nach einem anderen Modul zu suchen, wo das gleiche Problem auftrat (z.B. in Image::Magick), um dort "abzugucken". Das gilt nicht nur für XS-Code, sondern auch für das Makefile.PL oder andere Spezialitäten (wie die typemap). Mir persönlich hat das mehr geholfen als die gesamte Perl-Dokumentation. Klar - wer viele Module benutzt hat hier einen Vorteil.

Um noch mal auf das spezielle gettext-Beispiel zurückzukommen. Es ist so noch nicht wirklich einsatzbereit (genausowenig wie das ursprüngliche Locale::gettext auf CPAN), da es kaum Hilfen gibt, um aus Perl-Code die Texte zu extrahieren und in eine .pot-Datei zu schreiben, die ein Übersetzer bearbeiten kann. Wer sich dafür interessiert, findet im Gimp-Modul ein Programm names pxgettext (für "perl-xgettext"), das das normale xgettext-Programm ersetzt.


3. XS und Du und Ich

Der folgende, zweite Teil ist eher wie eine Art Kochbuch aufgebaut. Niemand benötigt alle folgenden "Tricks", um ein Modul zu schreiben. Deshalb geht es mir nicht darum, im Detail die Lösung zu vermitteln, sondern vielmehr einige interessante Probleme und ihre Lösungen zu beschreiben.

Mein Ziel ist es, zu zeigen, was möglich ist. Im Idealfall wird irgendwann jemand ein XS-Modul schreiben und auf ein Problem stossen, und sich dann daran erinnern, daß er das schon mal irgendwo gehört hat.

Wichtig ist, daß man weiß, daß etwas möglich ist, und man "nur" in der Dokumentation suchen braucht. Sonst werkelt man vielleicht an einer schlechten Lösung herum, ohne zu wissen, daß es besser geht.

3.1. Konstanten

Konstanten (z.B. LCMESSAGES) habe ich bewußt aus dem Beispiel im ersten Teil ausgeklammert. Die traditionelle Methode, C-Konstanten in den Perl-Interpreter zu befördern ist der AUTOLOAD/constant-Mechanismus, den h2xs normalerweise benutzt (der -A-Schalter hindert h2xs daran). Dabei wird ein Xs-funktion namens constant definiert, die anhand des Konstantennamens den Wert zurückliefert:

static int
constant(char *name)

    errno = 0;
    switch (*name) {
    case 'A':
#ifdef UUACT_COPYING
        if (strEQ(name, "ACT_COPYING")) return ACT_COPYING;
#else
        goto not_there;
#endif
    case 'B':
    case 'C':
        /* .. usw... */
    case 'Z':
    }
    errno = EINVAL;
    return 0;

not_there:
    errno = ENOENT;
    return 0;

Im Perl-Teil wird ein AUTOLOAD benutzt, um noch nicht definierte Konstanten zu "laden":

sub AUTOLOAD 
    # This AUTOLOAD is used to 'autoload' constants from the constant()
    # XS function.  If a constant is not found then control is passed
    # to the AUTOLOAD in AutoLoader.

    my $constname;
    ($constname = $AUTOLOAD) =~ s/.*:://;
    croak "& not defined" if $constname eq 'constant';
    my $val = constant($constname, @_ ? $_[0] : 0);
    if ($! != 0) {
        if ($! =~ /Invalid/) {
            $AutoLoader::AUTOLOAD = $AUTOLOAD;
            goto &AutoLoader::AUTOLOAD;
        }
        else {
                croak "Your vendor has not defined Locale::gettext macro $constname";
        }
    }
    no strict 'refs';
    *$AUTOLOAD = sub () { $val };
    goto &$AUTOLOAD;

Aus irgendeinem Grund verwendet keines meiner Module diesen Mechanismus. Am Anfang fand ich diesen Mechanismus nur unheimlich, inzwischen bin ich über einige unangenehme Nebeneffekte gestolpert und weiß, weshalb ich ihn nicht verwende:

$res == UUERR_NONE ? "ok" : "not ok";

Wird nur korrekt geparsed, falls UUERR_NONE schoneinmal verwendet wurde (z.B. in einem anderen Programmteil. Ist dies nicht der Fall, wird die Funktion (!) UUERR_NONE aufgerufen mit dem Resultat eines Pattern-Matching (?-Operator), der nicht geschlossen wird. Besonders böse wird dieser Fall, wenn man das ganze noch kommentiert:

$res == UUERR_NONE ? "ok" : "not ok"; # did it work?

So etwas passiert mir dauernd... solche Debugging-Sessions können Stunden dauern...

Natürlich muß es einen besseren Weg geben. Den gibt es auch, zumindest ab perl5.005: newCONSTSUB. Dies ist eine C-API-Funktion von Perl und wird normalerweise so eingesetzt:

BOOT:

   HV *stash = gv_stashpvn ("Gimp", 4, TRUE);

   newCONSTSUB (stash, "PARAM_BOUNDARY",newSViv(PARAM_BOUNDARY));
   newCONSTSUB (stash, "PARAM_CHANNEL", newSViv(PARAM_CHANNEL) );
   newCONSTSUB (stash, "PARAM_COLOR",   newSViv(PARAM_COLOR)   );
   newCONSTSUB (stash, "PARAM_DISPLAY", newSViv(PARAM_DISPLAY) );
   newCONSTSUB (stash, "PARAM_DRAWABLE",newSViv(PARAM_DRAWABLE));
   newCONSTSUB (stash, "PARAM_END",     newSViv(PARAM_END)     );

Ein "stash" entspricht in etwa einem Perl-Package (und ist im wesentlichen ein Hash der alle Package-globalen Objekte enthält). Die Funktion newCONSTSUB erzeugt eine neue Konstante aus einem Skalar (den man schnell mit newSViv o.ä. erzeugen kann). Diese Methode hat keinen der Nachteile von constant, und erlaubt ein angenehmeres Konstanten-Management (Konstanten, die zusammengehören, sind auch im Quellcode nahe beieinander; das Sortieren entfällt; man kann sie einfacher Programmatisch erzeugen).

Die Nachteile sind ein höherer Speicherverbrauch (ein Perl-sub ist relativ fett) und eine längere Ladezeit.

3.1.1. bootstrap und BEGIN

Ein ähnliches Problem wie constant gibt es auch beim Bootstrappen des Moduls. Wenn Perl z.B. Locale::gettext lädt, wird nur die .pm-Datei geladen. Erst der Aufruf bootstrap Locale::gettext lädt die XS-Funktionen und definiert diese auf der Perl-Ebene.

Da dies zur Laufzeit geschieht, bedeutet daß, das alle Benutzer eines Moduls die Funktionen (und vor allem die Prototypen) "sehen" können, außer dem Modul selbst.

Freunde des übermäßigen Klammernverbrauchs werden das nicht bemerken. Genaugenommen bemerkt niemand etwas, denn Perl kann mögliche Fehler durch falsche Parameterzahl etc. garnicht diagnostizieren. Abhilfe schafft ein BEGIN. Statt "normal" bootstrap aufzurufen, wickelt man es in ein BEGIN:

BEGIN  bootstrap Locale::gettext $VERSION 

In PDL z.B. liefen einige Module nach dieser Änderung nicht mehr, weil sie XS-Funktionen aufriefen und dabei falsche Parameter verwendeten.

3.2. Callbacks

Relativ häufig sind sog. "Callbacks", also Aufrufe von Perl-Funktionen aus einer C-Bibliothek heraus. Dabei treten zwei Probleme auf:

  1. "Was ist ein Callback": Eine Codereferenz oder ein Funktionsname, der irgendwie aufbewahrt werden muß.
  2. "Wie wird er aufgerufen": Wie sieht ein Perl-Funktionsaufruf in C aus.

Ersteres ist nicht schwer, es gibt jedoch eine Menge Stolperfallen. Es hat sich bewährt, eine globale Variable für den Callback zu definieren, und ihn auf undef zu setzen:

static SV *my_callback;

MODULE = ...     PACKAGE = ...

INIT: /* vor perl 5.6 muß es BOOT: sein! */
        my_callback = newSVsv (&PL_sv_undef);

void
set_my_callback(callback=0)
        SV *    callback
        CODE:
        sv_setsv (my_callback, callback);

Auf diese Weise erspart man sich jede Menge Spielchen mit dem Referenzzähler! set_my_callback kann man auf viele Weise benutzen:

set_my_callback sub  print "callback called!\n" ; # ein anonymous sub
set_my_callback \&my_perl_callback;                 # Funktionsreferenz
set_my_callback "my_perl_callback";                 # (nicht so gut!)
set_my_callback undef;                              # deaktivieren
set_my_callback;                                    # ditto

Der Aufruf sieht schon etwas komplizierter aus (Die perlcall-manpage enthält nicht wenige Hinweise...), gleicht aber immer diesem Beispiel:

static int
my_callback(int timestamp, char *infostring)

  dSP;
  int count;
  int retval;

  ENTER; SAVETMPS; PUSHMARK (SP);

  EXTEND (SP, 2);
  PUSHs (sv_2mortal (newSViv (timestamp)));
  PUSHs (sv_2mortal (newSVpv (infostring, 0)));

  PUTBACK;
  count = perl_call_sv ((SV *)cb, G_SCALAR);
  SPAGAIN;

  if (count != 1)
    croak ("perl-my_callback returned more than one argument");

  retval = POPi;

  PUTBACK; FREETMPS; LEAVE;

  return retval;

ENTER; SAVETMPS; PUSHMARK (SP); entspricht in etwa einer öffnenden geschweiften Klammer, PUTBACK; FREETMPS; LEAVE; schließt sie wieder.

Mit EXTEND (SP, 2) wird Raum für zwei Argumente geschaffen, die zwei folgenden PUSHs legen ein int und einen String als Argument auf den Perl-Stack.

Den eigentlichen Aufruf erledigt die Kombination PUTBACK/perl_call_sv/SPAGAIN. Das POPi nimmt den (einzigen) Rückgabewert vom Stack (den man immer aufräumen muß!).

perlcall enthält noch viele weitere Details, mit denen man sich belasten kann.

3.3. INCLUDE:

Eine nützliche Anweisung ist auch INCLUDE:. Klar ist, daß man mit ihr XS-Code "includen" kann. Ein eher unbeachtetes Feature (vieler Perl-Module!) ist, daß man auch andere Dinge als nur Dateien als Quelle benutzen kann. In einem meiner Module (Video::Capture::V4l) erzeuge ich z.B. die Mutator-Methoden für alle verwendeten Objekte durch ein Perl-Skript namens genacc:

INCLUDE: ./genacc |

Das "magische" open von Perl sorgt dafür, daß hier die Ausgabe des Programmes eingefügt wird. Damit kann man Dinge machen, von denen C-Programmierer normalerweise zurückschrecken, nämlich, ganz neue Sprachen entwicklen ;)

3.4. Aliase

Ein nettes Feature von XS sind die sogenannten Aliase. Wenn man eine XS-Funktion (ein sogenanntes "XSUB") definiert, kann man beliebig viele weitere Namen angeben, unter denen dieses XSUB in Perl erscheint. Das ist besonders nützlich für Funktionen, die eine recht aufwendige Umsetzung erfordern, sich aber sonst stark ähneln. In Gimp z.B. gibt es zwei Funktionen, gimp_install_procedure und gimp_install_temp_proc, die sich nur in ihrer Semantik, nicht in den Parametern unterscheiden.

Die Definition sieht so aus:

void
gimp_install_procedure(name, blurb, usw....)
        char *  name
        char *  blurb
        ...
        ALIAS:
                gimp_install_temp_proc = 1
        CODE:
        ... aufwendige initialisierungen ...
        if (ix)
          gimp_install_temp_proc (name, blurb, ...);
        else
          gimp_install_procedure (name, blurb, ...);

Man gibt jedem "Alias" eine Nummer. Beim Aufruf kann man dann in der Variablen ix nachsehen, welcher Alias aufgerufen wurde. Ist sie 0, wurde kein Alias (sondern die eigentliche Funktion) aufgerufen.

3.5. C_ARGS

Ein weiteres kleines Feature, das erst in Version 5.6 von Perl erscheinen wird, ist C_ARGS. Wenn man keinen CODE:-Abschnitt benutzt, erzeugt Perl selbst einen. Das spart mindestens 4 Zeilen ein. Vor Version 5.6 konnte man aber Argumente nicht umstellen, oder erzeugen: Manchmal gibt es Funktionen, die man in Perl "einfach anders" aufrufen würde, oder Funktionen, die Parameter verlangen, die man in Perl einfach berechnen kann.

Ein einfaches (nicht sonderlich sinnvolles) Beispiel ist memset: Es erwartet Adresse und Länge eines Speicherblocks. Möchte man eine Schnittstelle zu memset, die einen Perl-Skalar "setzt", kann man mit C_ARGS die Parameter für den Aufruf setzen, ohne den Aufruf selbst schreiben zu müssen:

void
memset(data, content)
        SV *    data
        int  content
        C_ARGS:
          SvPV_nolen (data), content, SvCUR (data)

3.6. Typemaps

Typemaps sind eines der arbeitssparendsten Werkzeuge, die XS zu bieten hat. Im gettext-Beispiel wurden C-Datentypen wie char * oder int benutzt, als wären sie "eingebaut" tatsächlich sind sie genausowenig in den XS-Compiler eingebaut wie SV * oder ein anderer Perl-Datentyp.

Stattdessen konsultiert der XS-Compiler (der übrigens xsubpp heißt, also in etwa "XSUB-Präprozessor") jedesmal, wenn er über einen Typennamen stolpert, eine Tabelle, die ihm sagt, wie man

  1. diesen C-Typ aus einem Perl-Skalar erzeugt (INPUT)
  2. aus diesem C-Typ einen Perl-Skalar konstruiert (OUTPUT)

Hier ist eine einfache Typemap (die in etwas ausführlicherer Form mit perl mitgeliefert wird):

int             T_IV

INPUT

T_IV
        $var = SvIV ($arg);

OUTPUT

T_IV
        sv_setiv ($arg, $var);

Der erste Teil ordnet jedem C-Typ eine Art Typkennung zu. Diese kann man frei erfinden (z.B. "Stefans_Object"). Der INPUT-Abschnitt legt fest, wie die Perl-Argumente in das XSUB "hineinwandern", der OUTPUT-Abschnitt wird benutzt, um C-Datentypen wieder an perl "auszugeben".

Die Definition ist in "ganz normalem" C geschrieben. $var und $arg sind Platzhalter und stehen für die C-VARiable und das Perl-ARGument. Außerdem gibt es noch den Platzhalter $type, der den Namen des C-Datentyps (z.B. int) bereithält, sowie $ntype, der den Typnamen in Perl-Form enthält (bei dem Unterstriche durch :: ersetzt wurden). Doppelte Anführungszeichen (") und andere Perl-Sonderzeichen ($, @, \) müssen außerdem gequoted werden.

Wenn man sich einmal die Typemap (bzw. einige der} Typemaps) ansieht, die zu Perl schon mitgeliefert wird (sie ist etwas gekürzt im Anhang abgedruckt), findet man einige interessante Datentypen (die im übrigen alle und vollkommen vollständig undokumentiert sind). Zwei sehr hilfreiche sind T_PTROBJ und T_PTRREF. Beide können sehr komplexe Typen aufnehmen (Zeiger auf structs, classen u.ä.). T_PTRREF erzeugt dafür einfach eine Referenz auf einen Skalar (der den Zeiger enthält), während T_PTROBJ ein echtes Perl-Objekt erzeugt. Als Beispiel soll folgende Typemap dienen:

Locale_gettext_state    T_PTROBJ

So kurz kann es sein. Schreibt man jetzt im C-Teil noch folgendes typedef:

typedef struct my_state Locale_gettext_state;

so kann man den neuen Datentyp Locale_gettext_state verwenden, der in Perl als Objekt vom Typ(!) Locale::gettext::state existiert (das wird mit $ntype erreicht). Steckt man jetzt noch ein paar Methoden in dieses Modul:

MODULE = Locale::gettext                PACKAGE = Locale::gettext::state

void
set_state_arg(state, arg)
        Locale_gettext_state state
        int  arg
        CODE:
        state->arg = arg;

void
DESTROY(state)
        Locale_gettext_state state
        CODE:
        free (state);

Kann man Objekte komplett und vor allem angenehm in XS implementieren, wobei der Typemap-Eintrag für das state-Objekt sogar eine Typprüfung für uns übernimmt. "No need to use perl anymore ;)".

3.6.1. Typemaps mit Haaren

Die Umsetzung von Locale_gettext_state zu Locale::gettext::state ist hilfreich, aber wie macht man das in einem Typemap-Eintrag? Einen kleinen Hinweis bekommt man, wenn man sich die Ähnlichkeit eines Typemap-Eintrages mit einem Perl-String vor Augen hält:

        sv_setiv ($arg, $var);

Das ganze ist nichts anderes, als ein String, in dem $arg und $var interpoliert wird! Den String kann man jederzeit beenden:

        sv_setiv ($arg, ". sin(2) ." + $var);

Der Ausdruck sin(2) wird in Perl (und vor allem während der Übersetzungszeit) ausgeführt. Auf diese Weise kann man sehr komplexe Typen (z.B. T_PTROBJ) erzeugen, die in ihrer Mächtigkeit sehr ähnlich zu generischen Typen (wie C++-templates oder Haskells Typsystem) sind.

Die INPUT-Definition für T_SimpleVal aus dem Gtk-Modul sieht z.B. so aus:

T_SimplePtr
        $var = Sv" . ($foo=$ntype, $foo=~s/://g, $foo=~s/^GtkGdk/Gdk/, $foo) . "($arg,0)

Er dient dazu, aus Gtk-Typnamen die entsprechenden Zugriffmakros zu erzeugen:

Datentyp Makro
Gtk::Gdk::Event SvGdkEvent
Gtk::Window SvGtkWindow

Hier wird noch ein weiteres Feature von XS verwendet: Der XS-Compiler ersetzt Doppelpunkte in Typennamen durch Unterstriche, wenn er die Typnamen aus der Typemap im C-Programm (jede XS-Datei wird in eine normale C-Datei umgewandelt). Gtk muß deshalb noch ein paar typedefs benutzen:

typedef GdkEvent *   Gtk__Gdk__Event;
typedef GTwWindow *  Gtk__Window;

Und schon kann man XS-Funktionen "fast wie in Perl" schreiben:

void
set_title(self, title)
        Gtk::Window     self
        char *       title
        CODE:
        gtk_window_set_title(self, title);

3.7. Makefile-Tips

Sie betreten jetzt die Typemap Chill Out Zone..... Die folgenden Tips sind eher praktischer Natur und drehen sich um das Makefile.PL.

3.7.1. Ein README generieren

Diesen Tip erhielt ich von Andreas König, nachdem ich mein allererstes Modul auf CPAN veröffentlicht habe, wie üblich ohne README. Andreas meinte, ich könnte aus meiner Moduldokumentation automatisch ein README generieren lassen, indem ich den dist-Parameter von WriteMakefile verwende:

WriteMakefile(
    'dist'   => 
                    PREOP       => 'pod2text Gimp.pm >README',
                   ,
    ...

PREOP gibt einen Shell-Befehl an, der vor dem Verpacken als Archiv aufgerufen wird (z.B. von make dist). Dieses README ist nicht so toll wie ein von Hand verfasstes sein könnte, aber es ist wesentlich informativer als viele READMEs auf CPAN, die einem nicht einmal sagen, was das Modul macht. Und vor allem ist es besser als gar keines.

Wenn wir schonmal dabei sind: man könnte auch gleich die Zugriffrechte der Dateien auf vernünftige Werte setzen, und gzip-Komprimierung schadet ebenfalls nicht:

    'dist'   => 
                    PREOP       => 'pod2text Gimp.pm >README && chmod -R u=rwX,go=rX .',
                    COMPRESS    => 'gzip -9v',
                    SUFFIX      => '.gz',
                   ,

3.7.2. .pm-Dateien und ihr Zielverzeichnis

ExtUtils::MakeMaker ist geradezu extrem unflexibel, wenn man mehrere Module in einem Paket vereinigt und noch dazu unverschämte Ansprüche stellt, wie z.B. bestimmte Module nur gegen bestimmte Bibliotheken linken möchte.

Prinzipiell muß man dann jedes Modul (also .pm+.xs-Dateien) in ein eigenes Unterverzeichnis packen, wobei man dann manuell angeben muß, welchen "Perl-Namen" ein Perl-Modul erhält, indem man das PM-Argument von WriteMakefile verwendet:

    'PM'     => 
                    'Gimp.pm'                => '$(INST_LIBDIR)/Gimp.pm',
                    'Net/Net.pm'     => '$(INST_LIBDIR)/Gimp/Net.pm',
                    'UI/UI.pm'               => '$(INST_LIBDIR)/Gimp/UI.pm',
                   ,

3.7.3. Abhängigkeiten zu anderen Modulen

Schon seit langem hat man die Möglichkeit, Abhängigkeiten zu anderen Modulen im Makefile.PL anzugeben:

    'PREREQ_PM'      => 
                    Gtk                 => 0.5,
                    PDL                 => 1.99,
                    Data::Dumper        => 2.0,
                    Parse::RecDescent   => 1.6,
                   ,

Früher gab das Makefile.PL beim Erzeugen des Makefiles nur ein paar Warnungen aus. Neuere Versionen von CPAN (warum heutzutage, wo es doch das CPAN gibt, immer noch Steinzeitmethoden wie manuelles ftp zur Installation von Modulen verwendet werden, ist mir schleierhaft ;), neuere Versionen von CPAN also erkennen diese Abhängigkeiten und installieren automatisch alle benötigten Module, so daß man von PREREQ_PM regen Gebrauch machen sollte.

Ein Problem kann das aber nicht lösen: viele Abhängigkeiten sind optional: Es wäre schön, wenn das entsprechende Modul vorhanden wäre, wenn es sich aber nicht übersetzen läßt ist es nicht so schlimm. Dies läßt sich leider (noch) nicht angeben.

3.7.4. Eigene Erweiterungen für das Makefile

Sehr häufig möchte/muß man eigene Regeln in das erzeugte Makefile aufnehmen. Dazu definiert man eine Funktion namens MY::postamble (irgendwie logisch... oder auch nicht). Ihr Rückgabewert wird ohne Änderung in das Makefile aufgenommen:

sub MY::postamble 
   <<PA
acc.c: genacc
        genacc >acc.c
PA

3.8. Devel:PPPort - Perl/Pollution/Portability

SvPV_nolen gibt es nicht in Perl 5.005, newCONSTSUB gibt es nicht in Perl 5.004, ... und wenn man ein neues Modul schreibt, weiß man das natürlich nicht, und die Benutzer älterer Perl-Versionen beschweren sich, weil es bei ihnen nicht läuft.

Als ich mit Kenneth Albanowski über dieses Problem sprach, meinte er, dies ginge ihm schon seit längerem durch den Kopf, und er schreibt (für das Gtk-Modul) einen Kompatibilitätsheader, der aber "zu gefährlich" für CPAN wäre. Nun ja, das war schnell ausgeräumt und nun gibt es Devel::PPPort, ein Modul mit einem untippbaren Namen, das sich noch nicht einmal installieren läßt.

Es besteht im wesentlichen aus einer C-Header-Datei, ppport.h, die sich auch als Perl-Skript ausführen läßt.

Die Benutzung geschieht normalerweise in den folgenden Schritten:

  1. neueste Perl-Version installieren.
  2. neueste Devel::PPPort-Version holen.
  3. ppport.h in sein Modulverzeichnis kopieren.
  4. ppport.h durchlesen und ausführen:

perl -x ppport.h *.c *.h *.xs foo/*.c etc..

ppport.h gibt hilfreiche Tips aus.

  1. Tips umsetzen und ppport.h in seinen Datein includen.

Viele Leute glauben, man sollte einfach Devel::PPPort installieren und bekäme automatisch Portabilität zu älteren Modulen. Das ist der Grund, weshalb Kenneth es für "gefährlich" hielt.

Vielmehr erlaubt es einem Modul-Programmierer, sein Modul an die jeweils aktuelle Perl-Version anzupassen, und dann eine größtmögliche Kompatibilität zu älteren Perl-Installationen zu bekommen.

Das klappt natürlich nicht mit allen Features, das Modul fängt aber die meisten Probleme ab, so daß man relativ ungehindert Programmieren kann.

3.9. "magic"

Die meisten Variablen in Perl verhalten sich ihrem Typ (Skalar, Array...) entsprechend. Einige jedoch (z.B. $!, %ENV oder jedes getiete Objekt) verhalten sich anders. Bei einem getieten Skalar beispielsweise wird bei jedem Lese- oder Schreibzugriff eine benutzerdefinierte Funktion aufgerufen.

Diese Objekte heißen "magisch", weil man ihnen ein Stück "magic" angeklebt hat. Magic ist, ganz handfest, eine Struktur, die bestimmte Zugriffe regelt und an ein Objekt angehängt werden kann.

Jede Art von "magic" wird durch ein Zeichen repräsentiert: 'E' z.B. ist die %ENV-Magic, 'S' dagegen die %SIG-Magic. Für XS-Module sind zwei Typen interessant: 'U' (Variablenzugriffe abfangen) und '~' (beliebig).

3.9.1. 'U'-Magic

Für den Typ 'U' muß man eine struct ufuncs bereitstellen, die drei Felder enthält: uf_val (wird bei lesenden Zugriffen aufgerufen), if_set (wird bei schreibenden Zugriffen aufgerufen) und uf_index (kann eine beliebige Zahl enthalten, die den Funktionen übergeben wird. Beispiel:

/* C-Teil */
static I32
my_get_function (IV index, SV *scalar)

  printf ("variable is being read\n");
  sv_setiv (scalar, rand());


static I32
my_set_function (IV index, SV *scalar)

  printf ("variable was set to new value\n");
  srand (SvIV (scalar));


/* XS-Teil */
void
attach_randomize_magic(sv)
        SV *    sv
        CODE:
        struct ufuncs uf;

        uf.uf_val = &my_get_function;
        uf.uf_set = &my_set_function;
        uf.uf_indef = 0;
        sv_magic (sv, 0, 'U', (char*)&uf, sizeof (uf));

Die XS-Funktion erwartet einen Skalar, und macht ihn "magisch". Danach wird für jede Zuweisung die C-Funktion my_set_function aufgerufen, für jeden Lesezugriff stattdessen die Funktion my_get_function. Danach verhält sich der Skalar wie ein Zufallszahlengenerator: Jeder Lesezugriff gibt einen neue Zufallszahl zurück, und jeder Schreibzugriff setzt den Startwert des Generators.

3.9.2. '~'-Magic

'~'-Magic ist etwas komplizierter. An sich macht dieser Typ überhaupt nichts, er kann dazu verwendet werden, Datenstrukturen an Skalar anzuhängen, die von Perl aus nicht angesprochen (und vor allem nicht zerstört) werden können.

Viel schöner ist aber die Möglichkeit, alle Arten von Zugriffen abfangen zu können, indem man die "Magic Virtual Table" (das ist so etwas wie die virtuelle Methodentabelle eines C++-Objektes). Dazu definiert man am besten eine Variable vom Typ MGVTBL, die (ähnlich wie struct ufuncs) fünf Zeiger auf Funktionen enthält:

Prototyp Beschreibung
int (*svt_get)(SV* sv, MAGIC* mg); Wird nach dem Lesen einer Variablen aufgerufen
int (*svt_set)(SV* sv, MAGIC* mg); Wird nach dem Setzen einer Variablen aufgerufen
U32 (*svt_len)(SV* sv, MAGIC* mg); Soll die Länge der Variablen zurückgeben
int (*svt_clear)(SV* sv, MAGIC* mg); Wird bei undef $var u.ä. aufgerufen
int (*svt_free)(SV* sv, MAGIC* mg); Wird aufgerufen, wnen die Variable zerstört wird

In Gimp beispielsweise verwende ich '~'-Magic, um eine C-Struktur wieder freizugeben, die nur so lange in Benutzung zu bleiben braucht, wie die entsprechende Variable in Perl genutzt wird. Das Erzeugen der '~' sieht folgendermaßen aus:

static MGVTBL vtbl_gdrawable = 0, 0, 0, 0, gdrawable_free;

static SV *new_gdrawable (GDrawable *gdr)

  SV *sv = newSViv ((IV) gdr);

  sv_magic (sv, 0, '~', 0, 0);
  mg_find (sv, '~')->mg_virtual = &vtbl_gdrawable;

  return sv_bless (newRV_noinc (sv), gdrawable_stash);

Bis auf svt_free sind alle Zeiger auf 0 gesetzt, d.h. die zugehörigen Funktionen werden nicht aufgerufen. Wenn das erzeugte GDrawable-Objekt zerstört wird (z.B. wenn der Programmablauf den Gültigkeitsbereich der Variable verläßt), wird die C-Funktion gdrawable_free aufgerufen:


   my $gdr = new_gdrawable (...)
   ...
 # <- hier wird C<gdrawable_free> aufgerufen

gdrawable_free gibt das GDrawable wieder frei (in Wirklichkeit wird es noch in einem Hash gecached und anderes mehr):

/* magic stuff.  literally.  */
static int gdrawable_free (SV *obj, MAGIC *mg)

  GDrawable *gdr = (GDrawable *)SvIV(obj);

  gimp_drawable_detach (gdr);

  return 0;

3.10. Austauschen von Informationen zwischen Modulen

Manchmal möchte man neben einer Schnittstelle auf der Perl-Ebene auch eine Schnittstelle auf der C-Ebene bereitstellen. PDL z.B. "exportiert" einige interne Funktionen für affine Transformationen, die auch rege benutzt werden (z.B. von Gimp oder PDL::Audio). Das Time::HiRes-Modul exportiert zwei Funktionen, mit denen die aktuelle Zeit abgefragt werden kann. Auch Event stellt seine gesamte API auch auf der C-Ebene zur Verfügung.

PDL z.B. macht dies, indem die Adresse einer struct mit Funktionszeigern in der Variablen $PDL::SHARE hinterlegt wird. Wird sie verändert kann man sein Programm abschreiben.

Um eine sichere Methode zu schaffen, um globale Werte hinterlegen zu können, wurde in Perl 5.005 eine globale Variable, HV *PL_modglobal eingeführt, in denen Module beliebeige Werte setzen können, die aber auf der Perl-Ebene nicht sichtbar ist.

PDL könnte z.B. folgende Methode benutzen, um den Zeiger auf die Funktionstabelle zu speichern:

/* Der Hash-Key (sollte den Modulnamen enthalten!) */
#define PDL_SHARE "PDL::SHARE"

/* Abspeichern */
hv_store (PL_modglobal, PDL_SHARE, strlen (PDL_SHARE), newSViv ((IV)func_ptr), 0);

/* Wiederfinden */
struct pdl_functions *share;
SV **share_ptr = hv_fetch (PL_modglobal, PDL_SHARE, strlen (PDL_SHARE), 0);

if (!share_ptr)
  croak ("PDL shared data block not found, please load the PDL module first!");

share = (struct pdl_functions *)SvIV (share);

3.11. INIT und CHECK

In Perl 5.6 wird der Compiler (B), der als experimentelles Feature schon in Version 5.005_03 dabei war, erstmals vollständig unterstützt. Für Modul-Programmierer (sowohl Perl und XS) wirft dies neue Probleme auf: Module, die in einem Programm geladen werden, führt der Compiler nur einmal aus: während der Compilierung.

Werden in der BOOT:-Sektion (oder auch im der .pm-Datei) Initialisierungen durchgeführt, die der Compiler nicht sehen kann (z.B. globale Variablen in C), gehen diese natürlich verloren.

Deshalb wurden zwei neue Funktionsnamen reserviert: Neben BEGIN und END werden auch die beiden Funktionen CHECK und INIT automatisch von Perl aufgerufen: Alle CHECK-Funktionen werden aufgerufen, wenn Perl die Compilierung beendet hat, aber bevor der Objektcode gespeichert wird. Die CHECK-Routine hat also die Möglichkeit, den internen Zustand in einer Perl-Variablen zu speichern.

Wird das (compilierte) Programm aufgerufen, werden all INIT-Funktionen aufgerufen. Dort können dann entweder die nötigen Initialisierungen wiederholt werden, oder es können Perl-Variablen (die in CHECK erzeugt wurden) dazu verwendet werden, den internen Zustand wiederherzustellen.

Ein Beispiel:

BEGIN         print "begin\n" 
CHECK    print "check\n" 
INIT     print "init\n"  
END   print "end\n"   

print "runtime\n";

Wird dieses Programm übersetzt, erscheint die folgende Ausgabe:

begin
check

Wenn das übersetzte Programm dann ausgeführt wird, erscheint:

init
runtime
end

Wird das Programm dagegen "nur" interpretiert, werden alle Funktionen aufgerufen:

begin
check
init
runtime
end

Wenn man also Perl 5.6 voraussetzen kann (das ist der eigentlich Knackpunkt), so kann man die gesamte Initialisierung in ein INIT (oder in einen INIT:-Abschnitt im XS-Teil) packen.


A. Listing - gettext.pm

package Locale::gettext;

use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);

require Exporter;
require DynaLoader;

@ISA = qw(Exporter DynaLoader);

# Items to export into callers namespace by default. Note: do not export
# names by default without a very good reason. Use EXPORT_OK instead.
# Do not simply export all your public functions/methods/constants.
@EXPORT = qw(

);
$VERSION = '0.01';

bootstrap Locale::gettext $VERSION;

# Preloaded methods go here.

1;
__END__
# Below is the stub of documentation for your module. You better edit it!

=head1 NAME

Locale::gettext - Perl extension for blah blah blah

=head1 SYNOPSIS

  use Locale::gettext;
  blah blah blah

=head1 DESCRIPTION

Stub documentation for Locale::gettext was created by h2xs. It looks like the
author of the extension was negligent enough to leave the stub
unedited.

Blah blah blah.

=head1 Exported functions

  extern char *gettext  (const char *msgid)    ;
  extern char *dgettext  (const char *domainname,
                            const char *msgid)    ;
  extern char *textdomain  (const char *domainname)    ;
  extern char *bindtextdomain  (const char *domainname,
                                  const char *dirname)    ;


=head1 AUTHOR

A. U. Thor, a.u.thor@a.galaxy.far.far.away

=head1 SEE ALSO

perl(1).

=cut

B. Die leicht gekürzte ExtUtils-Typemap

# basic C types
int                     T_IV
unsigned                T_UV
caddr_t                 T_PV
unsigned long *         T_OPAQUEPTR
char **                 T_PACKED
void *                  T_PTR
Time_t *                T_PV
SV *                    T_SV
SVREF                   T_SVREF
AV *                    T_AVREF

I32                     T_IV
I16                     T_IV
I8                      T_IV

#############################################################################
INPUT
T_SV
        $var = $arg
T_SVREF
        if (sv_isa($arg, \"$ntype\"))
            $var = (SV*)SvRV($arg);
        else
            croak(\"$var is not of type $ntype\")
T_UV
        $var = ($type)SvUV($arg)
T_IV
        $var = ($type)SvIV($arg)
T_INT
        $var = (int)SvIV($arg)
T_ENUM
        $var = ($type)SvIV($arg)
T_BOOL
        $var = (int)SvIV($arg)
T_NV
        $var = ($type)SvNV($arg)
T_DOUBLE
        $var = (double)SvNV($arg)
T_PV
        $var = ($type)SvPV($arg,PL_na)
T_PTR
        $var = INT2PTR($type,SvIV($arg))
T_PTRREF
        if (SvROK($arg)) 
            IV tmp = SvIV((SV*)SvRV($arg));
            $var = INT2PTR($type,tmp);
        
        else
            croak(\"$var is not a reference\")
T_REF_IV_REF
        if (sv_isa($arg, \"$type\")) 
            IV tmp = SvIV((SV*)SvRV($arg));
            $var = *($type *) tmp;
        
        else
            croak(\"$var is not of type $ntype\")
T_REF_IV_PTR
        if (sv_isa($arg, \"$type\")) 
            IV tmp = SvIV((SV*)SvRV($arg));
            $var = ($type) tmp;
        
        else
            croak(\"$var is not of type $ntype\")
T_PTROBJ
        if (sv_derived_from($arg, \"$ntype\")) 
            IV tmp = SvIV((SV*)SvRV($arg));
            $var = INT2PTR($type,tmp);
        
        else
            croak(\"$var is not of type $ntype\")
T_PTRDESC
        if (sv_isa($arg, \"$ntype\")) 
            IV tmp = SvIV((SV*)SvRV($arg));
            ${type}_desc = (\U${type}_DESC\E*) tmp;
            $var = ${type}_desc->ptr;
        
        else
            croak(\"$var is not of type $ntype\")
T_REFREF
        if (SvROK($arg)) 
            IV tmp = SvIV((SV*)SvRV($arg));
            $var = *INT2PTR($type,tmp);
        
        else
            croak(\"$var is not a reference\")
T_REFOBJ
        if (sv_isa($arg, \"$ntype\")) 
            IV tmp = SvIV((SV*)SvRV($arg));
            $var = *INT2PTR($type,tmp);
        
        else
            croak(\"$var is not of type $ntype\")
T_OPAQUE
        $var NOT IMPLEMENTED
T_OPAQUEPTR
        $var = ($type)SvPV($arg,PL_na)
T_PACKED
        $var = XS_unpack_$ntype($arg)
T_PACKEDARRAY
        $var = XS_unpack_$ntype($arg)
T_CALLBACK
        $var = make_perl_cb_$type($arg)
T_ARRAY
        $var = $ntype(items -= $argoff);
        U32 ix_$var = $argoff;
        while (items--) 
            DO_ARRAY_ELEM;
        
T_IN
        $var = IoIFP(sv_2io($arg))
T_INOUT
        $var = IoIFP(sv_2io($arg))
T_OUT
        $var = IoOFP(sv_2io($arg))
#############################################################################
OUTPUT
T_SV
        $arg = $var;
T_SVREF
        $arg = newRV((SV*)$var);
T_IV
        sv_setiv($arg, (IV)$var);
T_UV
        sv_setuv($arg, (UV)$var);
T_INT
        sv_setiv($arg, (IV)$var);
T_SYSRET
        if ($var != -1) 
            if ($var == 0)
                sv_setpvn($arg, "0 but true", 10);
            else
                sv_setiv($arg, (IV)$var);
        
T_ENUM
        sv_setiv($arg, (IV)$var);
T_BOOL
        $arg = boolSV($var);
T_DOUBLE
        sv_setnv($arg, (double)$var);
T_PTR
        sv_setiv($arg, (IV)$var);
T_PTRREF
        sv_setref_pv($arg, Nullch, (void*)$var);
T_REF_IV_REF
        sv_setref_pv($arg, \"$ntype\", (void*)new $ntype($var));
T_REF_IV_PTR
        sv_setref_pv($arg, \"$ntype\", (void*)$var);
T_PTROBJ
        sv_setref_pv($arg, \"$ntype\", (void*)$var);
T_PTRDESC
        sv_setref_pv($arg, \"$ntype\", (void*)new\U$type_DESC\E($var));
T_REFREF
        sv_setrefref($arg, \"$ntype\", XS_service_$ntype,
                    ($var ? (void*)new $ntype($var) : 0));
T_REFOBJ
        NOT IMPLEMENTED
T_OPAQUE
        sv_setpvn($arg, (char *)&$var, sizeof($var));
T_OPAQUEPTR
        sv_setpvn($arg, (char *)$var, sizeof(*$var));
T_PACKED
        XS_pack_$ntype($arg, $var);
T_PACKEDARRAY
        XS_pack_$ntype($arg, $var, count_$ntype);
T_DATAUNIT
        sv_setpvn($arg, $var.chp(), $var.size());
T_CALLBACK
        sv_setpvn($arg, $var.context.value().chp(),
                $var.context.value().size());
T_ARRAY
        ST_EXTEND($var.size);
        for (U32 ix_$var = 0; ix_$var < $var.size; ix_$var++) 
                ST(ix_$var) = sv_newmortal();
        DO_ARRAY_ELEM
        
        SP += $var.size - 1;
T_IN
        
            GV *gv = newGVgen("$Package");
            if ( do_open(gv, "<&", 2, FALSE, 0, 0, $var) )
                sv_setsv($arg, sv_bless(newRV((SV*)gv), gv_stashpv("$Package",1)));
            else
                $arg = &PL_sv_undef;
        
T_INOUT
        
            GV *gv = newGVgen("$Package");
            if ( do_open(gv, "+<&", 3, FALSE, 0, 0, $var) )
                sv_setsv($arg, sv_bless(newRV((SV*)gv), gv_stashpv("$Package",1)));
            else
                $arg = &PL_sv_undef;
        
T_OUT
        
            GV *gv = newGVgen("$Package");
            if ( do_open(gv, "+>&", 3, FALSE, 0, 0, $var) )
                sv_setsv($arg, sv_bless(newRV((SV*)gv), gv_stashpv("$Package",1)));
            else
                $arg = &PL_sv_undef;
        

C. Referenzen

Die Dokumentation zu allen Modulen auf CPAN kann man sich im Web unter der Addresse http://theoryx5.uwinnipeg.ca/CPAN/ ansehen.