UserId
Knowhow
23. Jan. 96
Hartwig Thomas
UserId
Knowhow
Endlich gibt es sie unter fast allen Umständen, aber ganz problemlos kommt man nicht dran: die UserId.
Irgendwie scheinen sich die Hersteller von Betriebssystemen ein besonderes Vergnügen daraus zu machen, für den Anwendungsprogrammierer wichtige Information möglichst schwer zugänglich zu halten. Schon 1978 unter MVS gab es die geheiligten 20 Zeilen 370er-Assemblercode, die dem Anwendungsprogrammierer in PL/I oder COBOL die leider oft so wichtige Information der UserId des momentan angemeldeten Benutzers in die Hand gab. Vor ein paar Wochen bin ich denselben 20 MVS-Zeilen begegnet: sie werden immer noch gebraucht, denn die Funktionen getpwuid() und getuid() sind unter MVS im ANSI-C immer noch nicht verfügbar.
Novell-UserId
Auf dem PC hatten wir bisher nur wenig mit UserIds zu tun, weil es sie im Einzelplatz-Windows einfach nicht gab. Manchmal drängte es sich auf, wenigstens die Novell-UserId zu beschaffen. Dies bereitete allerdings nur die Mühe, gültige Netware-Dokumentation aufzutreiben. Wir entschieden uns für die Verwendung der NWCALLS.DLL, die als zentrale Netware-Schnittstelle von Novell geliefert wird. Wenn man sich von dieser DLL und ihren Versionen nicht abhängig machen will, bereitet es keine Schwierigkeiten, dieselbe Funktionalität mit Hilfe von etwas Inline-Assembler zu implementieren [1]. Der Entscheid für die NWCALLS.DLL ermöglichte uns zudem Code zu schreiben, der auch funktioniert, wenn keine Novell-Umgebung vorliegt: Wir nehmen die Abwesenheit von NWCALLS als Anzeichen dafür, dass der Benutzer auf einem Einzelplatz-PC ohne UserId arbeitet und retournieren die spezielle UserId ANONYM. Damit diese Funktion GetNovellUserId() in beiden Fälle zufriedenstellen funktioniert, muss das dynamische Linken an die NWCALLS.DLL zur Laufzeit explizit mit Hilfe von Aufrufen von LoadLibrary(), GetProcAddress() und FreeLibrary() implementiert werden.
Der einzige bemerkenswerte Zwischenfall mit der ersten Version dieser Funktion zeigte sich darin, dass man die falsche UserId "Guest" zurückerhielt, wenn man das allgemein zugängliche Verzeichnis eines fremden Servers gemappt hatte. Wir hatten ursprünglich NWGetConnectionNumber() gemäss einem Novell-Beispiel zur Bestimmung der UserId mit NULL als Parameter aufgerufen. Dabei erhielten wir dann die letzte Connection, die über irgendeinen Attach aufgebaut worden war, statt der primären Login-Verbindung. Dieser Fehler ist weit verbreitet, wie man daran sieht, dass etwa der Norton-Desktop-Screensaver, der bei einer Grossbank in Zürich eingesetzt wird, nur durch Eingabe des letzten Attach-Passworts zum Entriegeln zu bewegen ist, während das Login-Passwort keinen Eindruck auf ihn macht. Normale Benutzer, die hauptsächlich auf einem einzigen Server arbeiten, merken von dieser Unzulänglichkeit glücklicherweise nichts. Als letzte Knacknuss ergibt sich nun für Benutzer von Windows 95, wie sie denn ein primäres Login bewirken können, statt eines blossen Attach. Antwort: In der Netzwerkumgebung auf dem Server mit der rechten Maustaste das Kontextmenü hervorzaubern. Dort gibt es so hübsche Sachen wie "Anmeldeinformationen", "Abmelden" und "Verbinden als", welche sich alle um den eigentlichen Login drehen und nicht nur um das Attach, das man unter "Netzlaufwerk verbinden …" serviert erhält.
Wer braucht schon Thunking …
Ich war eigentlich davon überzeugt gewesen, dass ich mir das letzte (!) Kapitel im Windows 95 Programmierhandbuch [2] schenken könne — wenigstens vorläufig. Noch mehr war ich davon überzeugt, dass ich höchstens mal etwas Thunking von unserem neuen 32bit Code zu alten 16bit DLLs brauchen würde. Es zeigt sich aber gerade im Falle der Bestimmung der UserId, dass das umgekehrte benötigt wurde. Bestehende 16bit-Software, die auch weiter auf 16bit-Windows läuft, soll zusätzlich auch auf 32bit-Windows eingesetzt werden und dort, bei Nichtvorhandensein einer primären Anmeldung bei einem Novellserver, die eigene Win32-UserId liefern.
Für eine 32bit-Anwendung ist dies weiter kein Problem: Man ruft einfach die API-Funktion GetUserName() und fertig. Eine 16bit-Anwendung muss sich nun mit dem Thunking auseinandersetzen.
Im Prinzip ist das ganz einfach, wie man dem erwähnten Thunking-Kapitel entnimmt. In der 16bit KRNL386.DLL unter Windows 95 oder Windows NT gibt es die Thunking-Funktionen LoadLibraryEx32W(), GetProcAddress32W(), CallProcEx32W() und FreeLibrary32W(). Mit denen geht man ähnlich um, wie beim expliziten dynamischen Linken mit ihren 16bit Namensvettern. Nur das CallProcEx32W() ist etwas speziell, weil man ihm in einer Bitmaske mitteilen muss, welche Parameter Zeiger sind, welche vom segmentierten (16:16) ins flache (0:32) Format umgewandelt werden müssen, worum es ja schliesslich beim Thunking geht. Dass man bei Verwendung dieser Funktionen kaum mehr Syntax- oder andere Hilfen vom Compiler (oder vom BoundsChecker) erhält, ist nur natürlich und dürfte auch bei der Einfachheit unserer Aufgabe kein Problem darstellen.
Damit man LoadLibraryEx32W() anwenden kann, muss man natürlich erst wissen, in welcher der Win32-DLLs die Funktion GetUserName() denn nun wirklich zu finden ist. Jeder Entwickler, der das Wort Schnellansicht (oder Quikview) noch nicht gehört hat, soll sofort alles stehn und liegen lassen, in der Systemsteuerung "Software" das "Windows-Setup"-Tab anklicken und dort beim letzten Punkt "Zubehör" die "Schnellansicht" seiner Installation hinzufügen (Installations-CD-ROM bereithalten!). Nach dieser Aktion kann er hier weiter lesen, wie man schnell bestimmt, welche Bezeichner von einer DLL (im 16bit NE-Format oder im 32bit PE-Format) exportiert werden: Mit der rechten Maustaste im Explorer auf dem Symbol der DLL das Kontext-Menü aktivieren. Dort den neuen zweiten Punkt "Schnellansicht" anwählen. Fertig. Auf diese Art gelang es mir denn zu bestimmen, dass GetUserName() in der ADVAPI32.DLL zu finden ist und dort unter dem Namen "GetUserNameA" exportiert wird. (Im Nebenhinein lernt man noch allerlei über neumödige Namemangling-Allüren des noch nicht so intensiv benutzten 32bit-Compilers ….)
WOW! Windows on Windows
Bleibt nur noch das Aufsuchen der richtigen Header-Datei, wo diese Thunking-Funktionen definiert sind, damit der Compiler nicht meckert. Nach Einsatz des äusserst nützlichen WGREP (every programmer ought to have one!) [3] findet sich WOWNT16.H dann im INCLUDE-Verzeichnis des 32bit-Compilers. Ist ja logisch, dass eine Headerdatei, die nur von 16bit-Programmen jemals gebraucht werden kann für den 32bit-Compiler installiert wird!
Unter Windows 3.1 solls aber auch laufen
Die ganze Sucherei nach der richtigen Headerdatei erwies sich dann als überflüssig. Wenn man WOWNT16.H oben einfügt und mit der Stublib des 16bit-Kernels linkt, dann funktioniert das Ganze natürlich nur für diejenigen KRNL386.DLLs, die den Win32-Betriebssystemen beiliegen. Die KRNL386.DLL des originalen 16bit-Windows kennt diese Thunking-Funktionen natürlich nicht. Wenn die UserId auch unter Window 3.1 bestimmt werden soll, ist es also unumgänglich auch für diese Funktionen das explizite Linking (jetzt wieder im 16bit-Stil) zu verwenden. Die KRNL386.DLL muss man wohl nicht laden, ein Aufruf von GetModuleHandle() liefert den Instance-Zeiger. Es ist jedoch entscheidend zu wissen, dass dieses Modul "KERNEL" und nicht etwas "KRNL386" heisst (Quikview I love you!). Die Funktion DoesThunking() in unserem lauffähigen Beispielcode prüft dann mit Hilfe von Aufrufen von GetProcAddress(), ob wir einen KERNEL besitzen, der die Thunking-Funktionen enthält, oder nicht. Weil wir nun nicht mehr implizit linken, brauchen wir WOWNT16.H nicht mehr, es leistet aber als Vorlage ganz gute Dienste für die Formulierungen der typedefs der Thunking-Funktionen.
Disclaimer
Der Beispielcode ist lauffähig und getestet. Trotzdem sollte er von Weiterverwendern nur als Vorlage genommen werden. Es wird etwa angenommen, dass bei Vorhandensein von Novell-UserId und Win32-UserId die Novell-UserId verwendet werden soll. Alle UserIds werden auf 6 Zeichen beschnitten, was im Zielumfeld, wo unsere DLL eingesetzt wird, das Richtige ist. Im Allgemeinen wird dies unerwünscht sein. Dieser Knowhow-Artikel soll nicht die ultimate GetUserId()-Funktion bekanntmachen, sondern hofft, mit einem anschaulichen und nützlichen Beispiel einen Einblick in die Freuden und Leiden des Thunking und die Mittel des Entwicklers, mangelhafte Dokumentation mit Geduld (in meinem Büro liegen immer noch jede Menge ausgeraufte Haare rum) und Werkzeugen (Quikview, WGREP) auszugleichen.
[1] Manfred Hill, Ralf Zessin: Netzwerk Programmierung in C, Novell Netware 3.X/4.X, IWT Verlag GmbH, Bonn [u.a.], 1995, ISBN 3-88322-491-X.
[2] Programmer’s Guide to Microsoft Windows 95, Key Topics on Programming for Windows from the Microsoft Windows Development Team, Microsoft Press, Redmond, 1995, ISBN 1-55615-834-3.
[3] Ich bin stolzer registrierter Benutzer der Shareware Windows Grep v1.5, die vom Autor Huw Millington, 71 Woodbury Avenue, East Grinstead, W. Sussex, RH19 3NY, England, für £10 erhältlich ist.