Neue Besen
Das Schreiben von Browser-Plug-ins gilt als komplizierte Materie, die nur wenige Adepten der schwarzen Magie beherrschen. Dabei ist das Schreiben eines Plug-in kein Hexenwerk.
- Joachim Baumann
Plug-ins sind ein eleganter Mechanismus, der einem Browser zusätzliche Funktionen verleiht. Sie sind klein, damit schnell herunterzuladen und verbinden Browser-Funktionen mit unbeschränkten Zugriffsmöglichkeiten auf die unterliegende Plattform. Wegen ihrer Vielseitigkeit lässt sich nahezu jede Störung im Zusammenspiel zwischen WWW-Server und lokalem Rechner beseitigen. Außerdem können sie etwa Daten applikationsspezifisch darstellen, lokale Anwendungen wie Microsoft Word „fernsteuern“ oder Inhalte für einen Drucker ohne Einflussnahme durch den jeweiligen Browser sauber aufbereiten.
Die Alternative zu Plug-ins sind im Normalfall Java-Applets. Während die benutzerseitige Bearbeitung von Daten mit Java-Applets gut realisierbar ist - allerdings mit dem Nachteil langer Download-Zeiten wegen deren Größe -, gerät schon die Steuerung lokaler Anwendungen mit Java zu einem komplexen Unterfangen. Selbst solch einfache Dinge wie das Ausdrucken von Inhalten können je nach der lokal installierten Version von Java zu einem regelrechten Abenteuer werden.
Der größte Nachteil ist, dass der Benutzer dem Autor des Plug-in blind vertrauen muss. Zwar kann der Autor den Code mit einem Zertifikat signieren, aber kein normaler (das heißt paranoider) Administrator eines Firmennetzwerkes dürfte dem wirklich vertrauen, sofern er es nicht selbst ausgestellt hat. Hinzu kommt, dass Plug-ins plattformspezifisch sind und damit nicht ohne Weiteres unter verschiedenen Betriebssystemen funktionieren. Außerdem hat Netscape die Plug-in-Schnittstelle definiert, was neuere Versionen der microsoftschen Browser nicht mehr unterstützen. Stattdessen existiert ActiveX Control, über das sich Netscape-Plug-ins in den Browser integrieren lassen (siehe Kasten „Das ActiveX-Control für den IE“).
Das ActiveX-Control fĂĽr den IE
Bis zur Version 5.5 SP 1 hat der Internet Explorer die Verwendung von Netscape-Plug-ins unterstĂĽtzt. FĂĽr diese UnterstĂĽtzung sorgte ein ActiveX-Control namens plugin.ocx, das die gesamte Schnittstellenimplementierung enthielt.
Um die selbst erstellten Plug-ins unter den neueren Versionen - ab IE 5.5 SP2 - benutzen zu können, muss man eine vom Mozilla-Projekt zur Verfügung gestellte DLL installieren, die genau das gleiche tut wie das nicht mehr ausgelieferte ActiveX-Control.
Dies ist nicht weiter kompliziert, aber etwas mühselig, da man den Quelltext auf dem eigenen Rechner übersetzen muss. Zuerst gilt es, mittels CVS den Quelltext der DLL zu holen und dabei darauf zu achten, dass die Konvertierung der Zeilenumbrüche von Unix- zur DOS-Kodierung stattfindet. Dies vergisst besonders leicht, wer CVS unter Unix oder unter Cygwin benutzt. Deshalb sei WinCVS als Arbeitsplattform empfohlen (siehe Kasten „Fundorte im Web“).
Der fĂĽr das Plug-in verwendete Pfad auf dem Server lautet mozilla/embedding/browser/activex/src/pluginhostctrl. Jetzt muss nur noch Visual C++ das gerade heruntergeladene Projekt ĂĽbersetzen, damit die DLL entsteht. FĂĽr die nachfolgende Registrierung im System sorgt ein einfacher Befehl, nachdem die DLL in das Systemverzeichnis von Windows kopiert worden ist:
regsvr32 pluginhostctrl.dll
Damit beschränkt sich der Einsatz von selbst geschriebenen Plug-ins im Normalfall auf die innerbetriebliche Umgebung. Gerade hier können sie ihre Stärken ausspielen, da es sich in der Regel um eine nahezu homogene Umgebung handelt.
Die Geister gerufen
Wer Plug-ins programmieren möchte, muss sich zuerst mit deren Lebenszyklus auseinander setzen. Grob betrachtet gibt es hier vier Abschnitte:
- Identifikation des benötigten Plug-in,
- Initialisierung des Plug-in,
- Verwendung durch den Benutzer,
- Entfernen des Plug-in.
Während nur der dritte dieser Lebensabschnitte für den Benutzer relevant ist, muss ein Programmierer alle im Auge behalten.
Innerhalb einer HTML-Seite kann der Entwickler beliebige Ressourcen referenzieren, indem er als HTML-Tag entweder EMBED oder OBJECT verwendet (Details unter „Die Einbettung des Plug-in“). Für die gewünschte Darstellung im Browser sorgt innerhalb des Tag ein MIME-Type (Multipurpose Internet Mail Extensions), der zur Identifikation des passenden Plug-in dient. Der Programmierer definiert ihn beim Erzeugen des Plug-in - im Falle „privater“ MIME-Types beginnen diese immer mit „X-. Beim Start erwartet der Browser die Plug-ins innerhalb eines spezifischen Verzeichnisses, das je nach Art des Hauses einen anderen Ort hat (siehe Kasten „Wo sucht der Browser nach Plug-ins?“). Für jedes Plug-in speichert er in einer Tabelle, welche MIME-Types er darstellen kann. Falls er später auf einen stößt, weiß er, welches Plug-in er zu laden hat. Je nach Browser kann eine Anpassung der verwendeten Plug-ins zur Laufzeit stattfinden.
Die Einbettung des Plug-in
Um Browser-übergreifend Plug-in-spezifische Ressourcen referenzieren zu können, verwendet man eine Verschachtelung von OBJECT- und EMBED-Tag:
<OBJECT classid=“CLSID:DBB2DE32-61F1-4F7F-BEB8-A37F5BC24EE2“ width=“100%“ height=“250“>
<PARAM name=“type“ value=“application/x-iX-plugin“/>
<PARAM name=“src“ value=“input-data“/>
<EMBED type=“application/x-iX-plugin“ name=“iX1“ width=“100%“ height=“250“>
</OBJECT>
Dies sorgt in den neueren Versionen des Internet Explorer dafür, dass er das ActiveX Control für Netscape Plug-ins referenziert (siehe „Fundorte im Web“) - wichtig hierfür ist der Parameter CLASSID. Ältere Browser, die mit dem OBJECT-Tag nichts anfangen können, laden mit dieser Konstruktion das Plug-in korrekt, ohne dass man auf die Vorteile des OBJECT-Tags verzichten muss.
Wo sucht der Browser nach Plug-ins?
Jeder Browser hat seinen eigenen Bereich für Plug-ins, was dazu führt, dass bei mehreren installierten Browsern im schlimmsten Fall genauso viele Kopien des Plug-in auf der Festplatte des Benutzers herumliegen. Wegen der im Mittel geringen Größe der Plug-ins spielt dies aber keine besondere Rolle. Das Verzeichnis, innerhalb dessen die verschiedenen Browser nach Plug-ins suchen, ist im Normalfall ein Unterverzeichnis des Installationsverzeichnisses. Bei Opera, Mozilla und Netscape Communicator heißt das Unterverzeichnis „plugins“, beim Internet Explorer „PLUGINS“. Im Normalfall kann selbst ein Programm dieses Installationsverzeichnis ohne Weiteres bestimmen.
Bei jeder Referenz entsteht eine eigene Instanz des Plug-in. Damit geht das Laden in zwei Schritten vor: Zuerst holt der Browser den Code von der Festplatte und ruft die Plug-in-globale Initialisierungsfunktion NP_Initialize() auf, mit der er Ressourcen belegt, die alle Instanzen des Plug-in gemeinsam benutzen.
Für jede Instanz in Folge aktiviert er die Funktion NPP_New(), die die jeweilige Instanz initialisiert und erhält unter anderem die im HTML-Text definierten Parameter.
Mit Macht auf allen Seiten
Nun beginnt die interessante Zeit im Leben des Plug-in. Je nach Aufgabe muss es jetzt innerhalb seines Bereiches zeichnen, vom Server Daten empfangen, Statusinformationen in die Statuszeile des Browsers schreiben oder anderes implementieren. Da es völlig ungeschützt im Kontext der Browser-Applikation läuft, kann es sämtliche Funktionen der unterliegenden Plattform verwenden; für die erwähnten Aufgaben reichen aber die Browser-internen.
Das Zeichnen von Informationen innerhalb des in der HTML-Seite reservierten Bereiches geschieht plattformabhängig. Für alle Plattformen gilt, dass die Funktion NPP_SetWindow() den Bereich setzt, innerhalb dessen das Plug-in zeichnen darf. Die übergebene plattformspezifische Struktur muss das Plug-in für die weitere Verwendung aufbewahren.
Unter Windows soll eine neue Funktion zur Bearbeitung von Events entstehen, die an den Bereich (das Fenster) gehen. Dies geschieht über einen Mechanismus, der „Subclassing“ heißt. Die neuen Funktion wartet auf das Ereignis WM_PAINT, das zum Neuzeichnen auffordert. Dieser Teil ist Standard-Windows-Programmierung; Details hierzu finden sich unter [1].
Zu den integralen Funktionen gehört der Empfang von Daten durch das Plug-in. Dies geschieht im Wesentlichen durch den wechselseitigen Aufruf zweier Funktionen, die dem Plug-in die Daten übermitteln: NPP_WriteReady(), fragt das Plug-in, wie viel Daten es zu übernehmen in der Lage ist, NPP_Write() übergibt dann maximal diese Menge an Daten (es können durchaus weniger Daten sein). Liegen neue Daten an, ruft der Browser erneut NPP_WriteReady() gefolgt von NPP_Write() auf, bis der Strom von Daten versiegt. Weiterhin gibt es noch NPP_NewStream() bei der Initialisierung und NPP_DestroyStream() beim Schließen des Stroms, die dem Plug-in die Chance geben, auf das jeweilige Ereignis zu reagieren.
Zum Anzeigen einer Meldung in der Statuszeile bietet der Browser NPN_Status() an, der eine Zeichenkette zur Anzeige ĂĽbernehmen kann. Der Browser stellt diese Meldung Browser-typisch so lange dar, bis eine neuere Meldung (auch von anderen Teilen der angezeigten Seite) diese ĂĽberschreibt.
Aufräumen zum Schluss
Wenn der Benutzer die Seite schließt, auf der sich die Plug-in-Instanz befindet, löst der Browser NPP_Destroy() aus. Hier kann der Programmierer instanzspezifische Aufräumarbeiten durchführen.
Falls es sich um die letzte Instanz des Plug-in handelt, kann der Browser mit NP_Shutdown() analog zur globalen Initialisierungsfunktion die von den Instanzen des Plug-in gemeinsam benutzten Ressourcen wieder freigeben.
Prinzipiell folgt das Schreiben eines Plug-in für fast alle Plattformen dem beschriebenen Weg. Dieser Beitrag beschränkt sich auf Microsoft Windows und auf ein einfaches Plug-in, das innerhalb der Statuszeile die Höhe und Breite seines eigenen Bereichs ausgibt. Für komplexere Interaktionen sei auf die Dokumentation im Web verwiesen, innerhalb derer die Verwendung der im Kasten „Plug-in-Funktionen“ erwähnten Funktionen zum Drucken und zum Kommunizieren mit dem Server detailliert erklärt sind.
| Plug-in-Funktionen | |
| Initialisierungs- und Freigabefunktionen | |
| NP_Initialize | initialisiert das Plug-in |
| NPP_New | initialisiert die spezifische Instanz |
| NPP_Destroy | entfernt die Instanz |
| NP_Shutdown | entfernt das Plug-ins aus dem Speicher |
| NPP_GetJavaClass | signalisiert die Verwendung einer assoziierten Java-Klasse |
| Normale Laufzeitfunktionen | |
| NPP_GetValue | fragt das Plug-in nach Information |
| NPP_SetValue | setzt Informationen ĂĽber das Plug-in |
| NPP_SetWindow | Modifikationen des Zeichenbereichs (Erzeugung, Größenänderung, Verschiebung, Entfernen) |
| NPP_HandleEvent | Behandlung der Events (nur Mac OS) |
| Druckfunktion | |
| NPP_Print | Anfordern einer druckbaren Version der dargestellten Daten |
| Lesen von Daten vom Server | |
| NPP_NewStream | informiert das Plug-in ĂĽber einen neuen Datenstrom. Die Anlieferung erfolgt mit NPP_Write und NPP_WriteReady |
| NPP_DestroyStream | Informiert das Plug-in ĂĽber das SchlieĂźen eines Datenstroms |
| NPP_StreamAsFile | liefert Dateinamen bei der Anforderung eines Datenstroms als Datei |
| NPP_URLNotify | erfolgt nach Beenden von NPN_GetURLNotify oder NPN_PostURLNotify |
| NPP_WriteReady | ermittelt, wie viele Daten das Plug-in mit dem nächsten Aufruf von NPP_Write verarbeiten kann |
| NPP_Write | liefert die nächsten Daten eines Datenstroms |
| Normale Laufzeitfunktionen | |
| NPN_GetValue | ermöglicht Zugriff auf das aktuelle Fenster (MS Windows) |
| NPN_SetValue | setzt Konfigurationsoptionen fĂĽr das Plug-in |
| NPN_Status | zeigt eine Nachricht in der Statuszeile des Browsers an |
| Speichermanagement | |
| NPN_MemAlloc | fordert Speicher vom Browser an |
| NPN_MemFree | gibt mit NPN_MemAlloc reservierten Speicher wieder frei |
| Schreiben von Daten zum Browser (die dann von diesem angezeigt werden) | |
| NPN_NewStream | erzeugt einen neuen Datenstrom |
| NPN_DestroyStream | schlieĂźt einen Datenstrom |
| NPN_Write | schreibt in den Datenstrom |
| Lesen von Daten vom Server | |
| NPN_GetURL | lädt eine HTML-Seite in ein Fenster (kann auch das aktuelle Fenster sein) |
| NPN_PostURL | schickt Daten mit der POST-Operation zum Server und zeigt das Resultat an |
| NPN_GetURLNotify | wie NPN_GetURL, ruft aber nach Beendigung des Requests NPP_URLNotify auf |
| NPN_PostURLNotify | wie NPN_PostURL, ruft aber nach Beendigung des Requests NPP_URLNotify auf |
| Diese Funktionen ruft der Browser auf (Callback-Funktionen). Sie sollten deshalb in jedem Plug-in vorhanden sein. | |
Für die Entwicklung eignen sich sowohl das Software Development Kit (SDK) von Mozilla als auch ein älteres von Netscape (siehe „Fundorte im Web“). Wer Mozillas SDK wählt, muss zuerst den gesamten Quelltext übersetzen, da das Kit die notwendigen Konfigurationsinformationen bestimmt. Die Alternative, das SDK von Netscape, stammt zwar aus dem Jahr 1997, unterstützt aber im Prinzip die gleichen Schnittstellen. Das Beispiel bezieht sich auf Letzteres.
| Fundorte im Web | |
| Dokumentation | devedge.netscape.com/library/manuals/2002/plugin/1.0/ |
| Netscape-Plug-in-SDK fĂĽr Windows | ftp.netscape.com/pub/sdk/plugin/windows/ |
| WinCVS | www.wincvs.org |
| ActiveX Control for Hosting Netscape plug-ins in IE | www.mozilla.org/projects/plugins/plugin-host-control.html |
| Cygwin | www.cygwin.com |
Im SDK verbirgt sich ein Verzeichnis mit Beispielen, die man sowohl für erste Gehversuche modifiziert als auch als Basis für die spätere Entwicklung verwenden kann; besonders empfehlenswert ist hierfür das Unterverzeichnis „WinTemplate“. Für eine erfolgreiche Plug-in-Entwicklung muss man npwin.cpp aus dem Verzeichnis „Common“ einbinden, um die Implementierung der Schnittstellenfunktionen zu erhalten.
Zum größten Teil besteht das Beispiel-Plug-in aus leeren Funktionsrümpfen (siehe Listing 1), was damit zusammenhängt, dass es abgesehen von der Ausgabe einer Statusmeldung keine weitere Funktion hat. Es zeigt aber, wie einfach grundsätzlich das Schreiben eines vollständigen Plug-in ist. Ein Plug-in mit komplexerer Funktionen dürfte im Normalfall ebenso übersichtlich bleiben; allerdings benötigt jedes nichttriviale Plug-in einen instanzspezifischen Speicherbereich. Dessen Verwaltung in NPP_New() und NPP_Destroy() erfordert aber nur einige zusätzliche Zeilen, denn der Zeiger auf diesen Bereich erscheint in der in jedem Funktionsaufruf mitgegebenen Instanzstruktur.
Listing 1: Beispiel fĂĽr ein Plug-in
#include <stdio.h>
#include "npapi.h"
/*
* Plugin-Initialisierung.
*/
NPError NPP_Initialize(void)
{ return NPERR_NO_ERROR; }
void NPP_Shutdown(void)
{ }
/*
* Instance-Initialisierung.
*/
NPError NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode,
int16 argc, char* argn[], char* argv[], NPSavedData* saved)
{ return NPERR_NO_ERROR; }
NPError NPP_Destroy(NPP instance, NPSavedData** save)
{ return NPERR_NO_ERROR; }
/*
* Ausgabe der Größe des Plugin-Bereiches,
* wann immer sie sich ändert.
*/
NPError NPP_SetWindow(NPP instance, NPWindow* window)
{
char message[256];
sprintf(message, "iX - Plugin: %d, %d", window->height, window->width);
NPN_Status(instance, message);
return NPERR_NO_ERROR;
}
/*
* Datenstromfunktionen (nicht benutzt)
*/
NPError NPP_NewStream(NPP instance, NPMIMEType type, NPStream *stream, NPBool seekable, uint16 *stype)
{ return NPERR_NO_ERROR; }
int32 NPP_WriteReady(NPP instance, NPStream *stream)
{ return 0xFFFF; } // Wir akzeptieren sehr groĂźe Puffer
int32 NPP_Write(NPP instance, NPStream *stream, int32 offset, int32 len, void *buffer)
{ return len; } // wir melden zurĂĽck, dass wir die Daten konsumiert haben
NPError NPP_DestroyStream(NPP instance, NPStream *stream, NPError reason)
{ return NPERR_NO_ERROR; }
void NPP_StreamAsFile(NPP instance, NPStream *stream, const char* fname)
{ }
void NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData)
{ }
/*
* Weitere Funktionen (nicht benutzt)
*/
void NPP_Print(NPP instance, NPPrint* printInfo)
{ return; } // Keine Druckfunktion
NPError NPP_HandleEvent(NPP instance, void* event)
{ return FALSE; } // Irrelevant, da nicht fuer Mac
jref NPP_GetJavaClass(void)
{ return NULL; } // Keine Java-Klasse assoziiert
Ausgehend von dem Beispiel kann jeder einfach iterativ die Funktionen erweitern. Einfache Varianten sind sind
- Ansteuerung von Microsoft-Office-Produkten ĂĽber DDE,
- Steuerung von Teilen der Windows-Plattform,
- Ă–ffnen eigener Fenster mit Darstellung von Server-Informationen,
- Steuerung beliebiger Applikationen mit Windows-Mechanismen.
Angesichts der Tatsache, dass das Plug-in ohne Sicherheitsbeschränkungen läuft, sind die einzigen Grenzen durch das eigene Vorstellungsvermögen und die Programmiermöglichkeiten unter Windows gegeben. Eine zusätzliche Randbedingung gilt unter Windows: der Name des Plug-in muss mit „NP“ beginnen, mit „.DLL“ enden und darf nur 8 + 3 Zeichen lang sein. Dies beschränkt die sinnvolle Namensgebung auf sechs Zeichen, was aber für den normalen Benutzer irrelevant sein dürfte.
Fazit
Zwar zwingt die Kürze des Artikels zum Unterschlagen einiger Kleinigkeiten, das Beispiel mag aber dennoch zeigen, dass das Schreiben eines Browser-Plug-in kein Hexenwerk ist, sondern nur eines Quäntchens Handwerkskunst bedarf, damit es zu brauchbaren Ergebnissen kommt.
Dies eröffnet eine weites Feld für die Unterstützung von webbasierten Applikationen im innerbetrieblichen Umfeld, was das Leben sowohl der Entwickler als auch der Benutzer ohne großen Aufwand deutlich vereinfachen kann. Sogar die Administration der Systeme innerhalb einer Organisation lässt sich komfortabler gestalten, da die Installation eines Plug-in nur einen Bruchteil dessen bedeutet, was die Installation einer Java Virtual Machine für verschiedene Browser mit sich bringt.
Zu den unbestreitbaren Nachteilen gehört: Plug-ins können ungeschützt auf das unterliegende System zugreifen, deshalb sollen sie außerhalb des eigenen Intranets im Normalfall nicht erlaubt sein. Für die skizzierten Einsatzfelder hat dies nur eine geringe Bedeutung. Mal als Nachteil, mal als Vorteil ist die plattformspezifische Programmierung zu sehen: der Programmierer muss sich mit dem System auskennen, um die gewünschten Ziele zu erreichen, die dann aber sehr weit reichen können. Java bietet mit seiner Plattformunabhängigkeit deutliche Vorteile, allerdings zu einem vergleichsweise hohen Preis.
Dr. Joachim Baumann
arbeitet im Bereich Web-Applikationsentwicklung der Firma junidas GmbH (www.junidas.de).
Literatur
[1] Charles Petzold, Programming Windows. The definitive guide to the Win32 API, Microsoft Press
iX-TRACT
- Browser-Plug-ins sind einfach zu schreiben und bei entsprechender Größe schnell ladbar.
- Die unbeschränkten Zugriffsmöglichkeiten auf die darunterliegende Plattform und auf die Browser-Funktionen können ein Sicherheitsrisiko darstellen.
- Im innerbetrieblichen Umfeld lassen sich Browser-Plug-ins vorteilhaft einsetzen.