Aufbruch in das XML-Land
Das Speichern der Programmeinstellungen in der Registrationsdatenbank hat sich zwar mittlerweile durchgesetzt, doch für mobile Anwendungen entstehen damit auch einige Nachteile. Beim Start auf fremden Systemen sieht z.B. SpeedCommander so jungfräulich frisch aus wie nach einer Neuinstallation. Man kann als Parameter zwar einen vorher exportierten Registrationsauszug übergeben, den SpeedCommander beim Start in die hiesige Registrationsdatenbank einträgt und nach dem Beenden selbständig wieder löscht. Allerdings verfallen hier wieder alle während des Betriebes geänderten Einstellungen. Auch bei einer Neuinstallation von Windows bzw. dem Zurückspielen eines Images sind alle davor geänderten Einstellungen wieder weg.
Mit dem Update des Xtreme Toolkit Pro auf die Version 9.60 führte Codejock ein cleveres System zur Speicherung von Einstellungen ein. Die Basis ist eine Klasse CXTPPropExchange, die alle grundlegenden Funktionen zum Lesen und Schreiben von Daten unterschiedlicher Typen enthält. Zur Umsetzung auf den jeweiligen Ausgabetyp sind davon dann die Klassen
- CXTPPropExchangeArchive
- CXTPPropExchangeRegistry
- CXTPPropExchangeXMLNode
- CXTPPropExchangeIniFile
abgeleitet. Sämtliche Objekte speichern ihre Daten über die Klasse CXTPPropExchange, ihnen ist es egal, ob die Serialisierung nun in eine binäre Datei, in die Registry, in eine INI-Datei oder in eine XML-Datei geschieht. Die Entscheidung dafür liegt dann in der Hand des Anwendungsprogrammierers.
Diese Idee hat mich so begeistert, dass ich für SpeedCommander 11 und Squeez 5 nun endlich einmal die Umstellung auf XML in Angriff genommen und nach einer Woche auch erfolgreich zum Abschluss gebracht habe. Fast alle Einstellungsdaten werden nun leserlich in eine XML-Datei geschrieben. Übrig bleiben nur noch die Tastenkürzel und die Einstellungen für das Menü und die Symbolleisten, die in binäre Dateien gespeichert werden. Zwar ist hier eine Umstellung auf XML auch möglich, allerdings ist es doch ein wenig Overkill, angepasste Symbole mit Base64 zu kodieren und in einer XML-Datei abzulegen.
Buildnummern enträtselt
Für die Installation von Software ist es äußerst wichtig, dass das Installationsprogramm unterscheiden kann, ob eine Datei neuer oder älter ist als eine eventuell bereits existierende Datei. Voraussetzung hierfür ist, dass die Dateien eine Versionsinformation besitzen.
Die Versionsnummer in der Versionsinformation besteht aus zwei 32-bit-Zahlen, die wiederum aus zwei 16-bit-Zahlen zusammengesetzt sind. Der leserliche Aufbau einer Versionsnummer ist 1.2.3.4, wobei gilt:
- 1 – Hauptversion
- 2 – Nebenversion
- 3 – Buildnummer
- 4 – Revision
Das Zustandekommen von Haupt- und Nebenversionsnummer dürfte selbsterklärend sein. Bei der Buildnummer gibt es verschiedene Ansätze. Einige Programmierer erhöhen die Buildnummer mit jedem Compilerlauf, andere für jedes veröffentlichte Release. Die Revisionsnummer wird dagegen recht wenig genutzt.
Bis einschließlich SpeedCommander 9 habe ich den zweiten Ansatz verfolgt. Die Buildnummer der ersten Version fing immer mit dem Hunderfachen der Hauptversion an, theoretisch konnte es also bis zu 100 verschiedene Versionen einer Hauptversion geben. Ganz so weit ist es aber nie gekommen.
Mit SpeedCommander 10 bin ich dazu übergegangen, die Buildnummer als Pseudonym für den Tag zu benutzen, an dem die Anwendung oder die DLL kompiliert wurde. Bezugspunkt ist der 1. September 1993 – der Tag meiner Gewerbeanmeldung. Die Buildnummer gibt also die Anzahl der Tage nach dem 1. September 1993 an. So kann man also ganz genau nachvollziehen, an welchem Tag das jeweilige Modul durch den Compiler gelaufen ist.
Der erste (Nicht-Betatester), der die Buildnummer für heute errechnet und hier in einem Kommentar hinterlässt, bekommt eine SpeedCommander-Lizenz spendiert.
Gugeliges (Teil 1)
Es ist immer wieder interessant, mit welchen Suchausdrücken man zu diesem Blog stoßen kann. Hier einmal eine Auswahl der ungewöhnlichsten Eingaben:
Bevormundung der (tele)komischen Art
Gestern wurde der Telefonanschluss meiner Mutter auf ISDN umgestellt. Eigentlich wollte sie ja einen DSL-Anschluss, die hochmodernen Glasfaserkabel der letzten Meile machten dies aber leider unmöglich. Wer sich einmal mit einem analogen Modem und 28000 kbps durch das heutige Internet bewegt hat, der wird mir bestätigen, dass dies nicht wirklich ein Vergnügen ist. In diesem Fall ist auch ISDN mit 64000 kbps schon ein großer Fortschritt.
Die Umschaltung selbst erfolgte völlig problemlos, als kleine Telefonanlage und ISDN-Modem kommt eine Eumex 220 PC zum Einsatz. Die neuen Rufnummern wurden anscheinend völlig automatisch in die Eumex eingetragen, nach dem Anstecken des Telefons klappte das Telefonieren samt Rufnummeranzeige ohne weitere Konfiguration.
Mit dem Einspielen der Software wurden in Windows XP zwei ISDN-Modems eingerichtet, das Einrichten einer neuen DFÜ-Verbindung war auch schnell erledigt. Die analoge Einwahl erfolgte bisher immer über Tele2, also fix die 0 19 36 844 samt Benutzernamen und Kennwort eingetragen. Nach dem Wählen kam die Meldung: „Fehler 678. Die Verbindung konnte nicht hergestellt werden, weil der Remotecomputer die Verbindungsanforderung nicht beantwortet hat.“ Funktionierten die ISDN-Modemtreiber etwa nicht richtig oder war ein falsches Protokoll eingestellt? Nach intensiver (telefonischer) Überprüfung konnte das aber ausgeschlossen werden. Zum Test wurde eine Verbindung zu T-Online mit der Zugangsnummer 0 19 10 11 eingerichtet und die Benutzerdaten mit Dummywerten gefüllt. Nach dem Wählen kam die von T-Online erwartete Fehlermeldung, dass die Anmeldedaten nicht verifiziert werden konnten. Dies zeigte, dass technisch zumindestens alles in Ordnung war.
Die Daten der T-Online-Verbindung wurden wieder durch die Tele2-Daten ersetzt, aber erneut zeigte sich Fehler 678. Hatte Tele2 vielleicht unterschiedliche analoge und digitale Einwahlnummer? Ein Anruf bei der Tele2-Hotline erbrachte die Bestätigung, dass es nur eine Einwahlnummer gibt. Die zwei neuen MSN wurden sicherheitshalber auch noch bei Tele2 eingetragen, und nach spätestens zwei Stunden sollte die Einwahl klappen. In der Zwischenzeit suchte ich über onlinekosten.de alternative Call-by-Call-Zugänge zum Testen heraus. Gleich der erste Zugang der Firma a c n über die Rufnummer 0 19 19 32 34 funktionierte wieder. Der anschließende Test über Tele2 brachte aber erneut keinen Erfolg. Auch der Versuch über MSN Easysurfer mit der Rufnummer 0 19 36 70 schlug fehl.
Eine Suche über Google brachte dann den Tip, doch einmal in die Sperrliste der Eumex zu schauen. Das taten wir dann auch und mussten erstaunt feststellen, dass dort neben der 0190 auch die 0192 und 0193 eingetragen war. Alle Call-by-Call-Zugänge, die nicht funktionierten, fangen mit 0193 an, T-Online und ACN beginnen dagegen mit 0191. Kein Wunder, dass der Remotecomputer nicht reagierte, er wurde ja auch überhaupt nicht gefragt. Erwartungsgemäß funktionierten alle Call-by-Call-Zugänge nach dem Löschen der 0193-Sperre wieder.
Es ist ja durchaus begrüßenswert, dass die Telekom sich um die Abzocke der Kunden über Dialer und teure Hotlines besorgt zeigt und die 0190-Vorwahlen per Vorbelegung sperrt. Allerdings ist es schon recht unverschämt, dass ohne jeden Hinweis pauschal auch gleich die Einwahlnummern der Mitbewerber gesperrt werden. Dieser Hinweis sollte natürlich auch für die 0190-Vorwahlen erfolgen, denn viele Hotlines gehen heutzutage nun einmal über die 0190. Das hätte uns einiges an Aufregung und Telefongebühren erspart.
Einstellungssache
In der letzten Zeit war ich sehr mit dem Redesign der Einstellungsdialoge von SpeedCommander beschäftigt. Man denkt eigentlich, dass es doch gar nicht so schwer ist, ein paar Dialogelemente im Fenster zu verteilen, aber da steckt einiges an Arbeit drin.
Ein Nachteil der bisherigen Implementation ist, dass die Einstellungen in einzelne Dialoge aufgeteilt sind und der Anwender somit viel zu viel mit dem Öffnen und Schließen der Dialogfenster beschäftigt ist. Ein guter Freund überzeugte mich auch, dass die derzeitige Anordnung eher so aufgebaut ist, wie es der Programmierer sieht; aber nicht so, wie es der Anwender gerne hätte. Er erklärte sich bereit, mit mir zusammen die ganze Sache einmal auf neue Füße zu stellen.
Wir haben die einzelnen Einstellungen nach dem Muster Aussehen, Verhalten und Erweitert aufgeteilt und dann in zusammengehörige Gruppen sortiert. Die zeitintensivste Arbeit war dann aber das Dialogdesign selbst. Die Dialoge sollen schließlich gut zu bedienen sein, schick aussehen und sich auch an den Styleguide von Microsoft halten.
Was stört in Visual Studio 2003?
Obwohl das Visual Studio 2003 nun schon seit 2 Jahren auf dem Markt ist, arbeite ich immer noch mit Visual C++ 6.0. Bisher habe ich mich zweimal an den Umstieg herangewagt, aber jedesmal bin ich reumütig zum VC6 zurückgekehrt. Die Gründe für die Unzufriedenheit liegen nicht im Compiler und im Debugger, denn die sind eine wirkliche Verbesserung. Das Problem ist die IDE selbst, der man anmerkt, dass sich ihre Zielgruppe eher aus .netten Entwicklern zusammensetzt.
Da ich immer wieder verwundert gefragt werde, warum ich immer noch am VC6 hänge, habe ich einmal das aufgelistet, was das effektive Arbeiten im VS2003 enorm behindert:
- Nach der Bearbeitung eines Dialoges verschwindet das Tool-Fenster nicht wieder automatisch
- Im VC6 bekam man im Dialogeditor mit <Enter> den Eigenschaftendialog (z.B. für ein Kontrollkästchen) angezeigt und konnte dann den Text ändern. Im VS2003 löscht <Enter> einfach den gesamten Text (auch noch ohne Undo!). Ist mir desöfteren passiert und nervt ungemein!
- Die Stringtabelle wird nicht sortiert, bei jeder Anzeige muss man dies jedesmal per Hand machen.
- Beim Einfügen von neuen Strings werden diese ständig hinten angehangen, der VC6 fügt sie an der entsprechenden Stelle ein.
- Im VC6 wurden in der Stringtabelle alle 16 Einträge eine Linie gezeichnet, das hat die Übersichtlichkeit sehr erhöht.
- Die Eigenschaften (z.B. für einen Button) sind jetzt alle in einem Eigenschaftenfenster. Im VC6 waren sie logisch gruppiert, daher sucht man im VS2003 jedesmal nach der richtigen Option.
- In meinen Projekten verwende ich einige Ordner, um die Dateien zu gruppieren. VS2003 sortiert ab Ebene 2 grundsätzlich die Dateien vor die Ordner. So sucht man jedesmal nach einer Datei, weil sie nicht da ist, wo man sie vermutet.
- Wenn man mehrere Ordner in einem Projekt aufgeklappt hat, dann merkt sich VS2003 den Aufklappstatus (auch nach dem Beenden). Ich bin es gewohnt, dass die Ordner beim nächsten Mal wieder alle geschlossen sind.
- Mit F4/Umschalt+F4 bewegt man sich nach dem Kompilieren durch die einzelnen Fehler. Im VC6 wird der Cursor automatisch auf die jeweilige Zeile gesetzt, im VS2003 bleibt der Fokus auf der Fehlerliste und man muss nach F4 jedesmal <Enter> drücken, um in das Quellcodefenster zu gelangen.
- Beim Suchen durch Dateien konnte man sich im VC6 auch mit F4/Umschalt+F4 durch die Ergebnissliste bewegen und die entsprechende Datei wurde mit der Fundstelle geöffnet.
- Im Dialogeditor werden beim Design nur Tastenkürzel angezeigt, wenn die globale Windows-Option ‚Unterstrichene Buchstaben für Tastaturnavigation ausblenden (mit Alt-Taste einblenden)‘ aktiviert ist. Ich habe diese standardmäßig ausgeschaltet, und damit kann man die Tastenkürzel für die einzelnen Elemente nur durch Raten vergeben.
- Bei den Projektoptionen ist es nicht möglich, die Optionen für mehrere Projekte gleichzeitig zu ändern. So muss man den Dialog jedesmal öffnen/schließen.
- Der Build-Befehl gilt immer für das Projekt, was gerade im Solution-Explorer bzw. im Quelltextfenster aktiv ist. Im VC6 wählt man ein Startprojekt aus und die Befehle gelten dafür. Allerdings gibts für VS2003 auch ein entsprechendes AddIn (Fast Solution Build), das funktioniert aber nicht immer zu 100%.
- Wenn man eine Datei zum Bearbeiten aus der Quellcode-Verwaltung auscheckt, dann braucht man im VC6 im Bestätigungsdialog nur <Enter> drücken. Im VS2003 ist da aber ein mehrzeiliges Kommentarfeld und <Enter> geht dann natürlich dahin. Kann man zwar hart in den Ressourcen ändern, stört aber.
- Für den VC6 nutze ich WndTabs, das die geöffneten Dateien logisch gruppiert und ständig sichtbar hält. Im VS2003 hat man nur eine Leiste und muss ständig mit der Maus hin- und herscrollen. Dazu wechselt <Strg+Tab> anhand der MDI-Reihenfolge und nicht anhand der in der Tableiste angezeigten Reihenfolge.
- Das wichtigste: Der Class-Wizard fehlt, alles geht nun umständlich über Eigenschaftenfenster. Die überschriebenen Methoden und Windows-Nachrichtenhandler werden nicht mehr geordnet in die Header-Datei geschrieben, im VC6 gab es jeweils dafür bestimmte Sektionen. Meine Projekte gleichen sich in den Hauptklassen und Dateinamen (z.B. MainFrame.cpp). Mir ist es hier öfters passiert, dass der neue Handler in einem anderen Projekt eingefügt wurde, wenn die Datei- und Klassennamen in mehreren Projekten vorkommen. Dazu wird in der Quelltextdatei „#include „.\MainFrame.h“ eingefügt, obwohl „#include „MainFrame.h“ schon vorkommt.
Leider macht es einen Microsoft immer schwerer, mit dem VC6 weiter zu arbeiten. Mit den LIB-Dateien vom letzten Platform SDK für XP SP2 kann der VC6 nicht mehr umgehen und auch mit den Symboldateien vom SP2 kommt er nicht mehr klar. Mein großer Wunsch ist immer noch, dem VC6 die Fähigkeit verpassen zu können, mit dem neuen PDB-Format des VS2003 umgehen zu können. Dann könnte man einfach den Compiler und Linker austauschen und wäre wieder auf dem neuesten Stand. Aber das wird wohl leider nur ein Wunschtraum bleiben.
In den nächsten Wochen werde ich einmal testen, inwieweit die aktuelle Preview von VS2005 das eine oder andere Problem abstellt oder ob ich auch in Zukunft weiter auf den VC6 zurückgreifen muss.
Wurzelproblem im Netzwerk
Ein Betatester machte mich gestern auf ein fehlerhaftes Verhalten von SpeedCommander in Zusammenhang mit gesperrten Tabs und UNC-Namen aufmerksam. Wenn man sich ein Tab mit einem UNC-Namen als Basisordner definiert und den Laufwerkswechsel sperrt, dann aktiviert SpeedCommander unter Umständen auch ein freies Tab, wenn man in einen Ordner innerhalb der gleichen Freigabe wechselt.
Im Debugger war zu erkennen, dass die Funktion PathIsSameRoot anscheinend falsche Ergebnisse liefert. SpeedCommander prüft mit dieser Funktion, ob sich zwei Ordner auf dem gleichen Laufwerk befinden:
// Laufwerkswechsel ist gesperrt if (pTabLayoutFolder->m_dwFlags & FOLDERTAB_LOCKDRIVES) { // Kein Basisordner oder ungleiches Laufwerk if (!Lwl_PathIsSame(pTabLayoutFolder->m_strBaseFolder, strNewFolder) && !PathIsSameRoot(strCurrentFolder, strNewFolder)) { // Wechsel zum freien Tab if (-1 != iFirstUnlockedTab || fCreateNewTab) goto DisplayFolder; // Fehlermeldung und weg ShowInfoTip(IDS_TAB_INFOTIP_LOCKDRIVES); return FALSE; } ... }
Wenn sich die beiden Ordner auf unterschiedlichen Laufwerken befinden, dann wird bei gesperrtem Laufwerkswechsel entweder auf das erste nicht gesperrte Tab gewechselt oder ein InfoTip angezeigt und der Wechsel verhindert.
PathIsSameRoot bekommt die zu testenden Ordner als Parameter übergeben und gibt TRUE zurück, wenn sich beide Ordner auf das gleiche Laufwerk beziehen. Bei den Ordnern C:\Windows und C:\Programme wird TRUE zurückgegeben, während bei C:\Windows und D:\Programme FALSE zurückgegeben wird.
Bei UNC-Pfaden ist die Freigabeebene die Root, also z.B. \\Server\Daten. PathIsSameRoot prüft bei UNC-Pfaden also, ob sich beide übergebenen Ordner auf die gleiche Freigabe beziehen:
\\Server1\Daten\Test und \\Server1\Daten\Test2
—> Gleiche Root\\Server1\Daten\Test und \\Server1\Ablage\Test
—> Ungleiche Root\\Server1\Daten\Test und \\Server2\Daten\Test2
—> Ungleiche Root\\Server\Daten\Test und \\Server\Daten
–> Gleiche Root\\Server\Daten und \\Server\Daten\Test
—> Ungleiche Root (Falsch!)
Das letzte Beispiel zeigt, dass die Funktion PathIsSameRoot offensichtlich Probleme hat, wenn der erste Parameter einen Freigabeordner (also eine Root) darstellt. Die beiden Parameter sind die gleichen wie im Beispiel davor, nur die Reihenfolge ist geändert. Doch warum gibt die Funktion hier unterschiedliche Ergebnisse zurück?
Schauen wir uns einfach einmal die WINE-Implementation der Funktion an, die etwas anschaulicher ist als der Assembler-Quelltext aus dem Debugger:
BOOL WINAPI PathIsSameRootA(LPCSTR lpszPath1, LPCSTR lpszPath2) { LPCSTR lpszStart; int dwLen; TRACE("(%s,%s)\n", debugstr_a(lpszPath1), debugstr_a(lpszPath2)); if (!lpszPath1 || !lpszPath2 || !(lpszStart = PathSkipRootA(lpszPath1))) return FALSE; dwLen = PathCommonPrefixA(lpszPath1, lpszPath2, NULL) + 1; if (lpszStart - lpszPath1 > dwLen) return FALSE; /* Paths not common up to length of the root */ return TRUE; }
Die Funktion prüft, ob lpszPath1 und lpszPath2 gültige Zeiger sind. PathSkipRoot liefert einen Zeiger auf das erste Zeichen, das der Root folgt oder NULL, wenn keine Root ermittelt werden konnte. Mit PathCommonPrefix wird die Anzahl der gleichen Zeichen ermittelt, mit denen beide Ordnernamen anfangen. Anschließend wird geprüft, ob die Anzahl der gemeinsamen Zeichen mindestens so groß ist wie die Länge der ermittelten Root von lpszPath1. Wenn ja, dann zeigen beide Pfade auf die gleiche Root.
Die Schwachstelle scheint hier PathSkipRoot zu sein. Wenn man die Funktion einmal jeweils direkt mit C:\ und \\Server\Freigabe aufruft, dann gibt PathSkipRoot bei C:\ einen Zeiger auf das abschließende NULL-Zeichen zurück, bei \\Server\Freigabe jedoch einen NULL-Zeiger. Das veranlasst PathIsSameRoot, die Abarbeitung zu beenden und FALSE zurückzugeben.
Doch selbst wenn Microsoft diesen Fehler im nächsten Service Pack fixen sollte, so wird es immer noch eine große Anzahl von Windows-Systemen geben, bei denen eine SHLWAPI.DLL mit einer nicht korrekten Implementierung von PathIsSameRoot Verwendung findet. Die einzige Möglichkeit, den Fehler in der eigenen Anwendung dauerhaft zu beheben, ist daher die Umgehung von PathIsSameRoot und die Implementation einer eigenen Funktion:
BOOL Lwl_PathIsSameRoot(LPCTSTR pszPath1, LPCTSTR pszPath2) { // Pruefen auf Gueltigkeit if (NULL == pszPath1 || NULL == pszPath2) return FALSE; // Temporaere Kopien erstellen TCHAR szPath1[MAX_PATH+1], szPath2[MAX_PATH+1]; StringCchCopy(szPath1, countof(szPath1), pszPath1); StringCchCopy(szPath2, countof(szPath2), pszPath2); // Beide Pfade bis auf die Root kuerzen if (!PathStripToRoot(szPath1) || !PathStripToRoot(szPath2)) return FALSE; // Root von beiden Pfaden vergleichen return (0 == lstrcmpi(szPath1, szPath2)) ? TRUE : FALSE; }
Das gleiche gilt leider auch für PathSkipRoot und alle Funktionen, die PathSkipRoot intern verwenden. Hier steht man nun vor der Entscheidung, generell auf die doch sehr komfortablen Pfadfunktionen zu verzichten oder darauf zu hoffen, dass es keine negativen Auswirkungen von PathSkipRoot auf die anderen Funktionen gibt.
Schafe helfen nicht
Es war kalt damals. Herbst und so ein Wetter, wie das nur Herbste draufhaben. Baumblätter lagen ertrunken in Pfützen, und ein Nordost blies durch die kahlen Zweige. Ich stand auf der Straße und weinte. Ich sah auf das Licht, das aus der Wohnung meiner Eltern kam, ganz gelb und warm, ich wußte, daß da mein Bett stand, und ich weinte noch mehr, denn ich war sechs und mir war verdammt klar, daß ich da nicht mehr hingehörte. Ich würde alleine auf der Straße bleiben, mich mit nassem Laub decken, um am nächsten Morgen auf einem Schiff anheuern. Ich habe vergessen, warum ich von zu Hause weg wollte. Aber ich erinnere mich, daß Kinder sehr unglücklich sein können. Vielleicht unglücklicher als erwachsene Menschen, weil sie nicht wissen, was das für ein Schmerz ist, der mit der Trauer kommt, und ob der nicht vielleicht für immer bleibt.
Herbst war es, und ich ging langsam ein paar Schritte weg von dem Licht, von dem Haus, und ich machte mein Gesicht ganz fest. Und dann war da auf einmal dieses Schaf. Von Gott geschickt oder von grausamen Eltern aus dem Stall getrieben, lag es auf der Straße. Dreckig und ungeliebt. Ich hob das Tier auf und so standen wir da rum, in der Nacht. Das halbblinde Stoffschaf und ich. Viel später sind wir mit dem letzten Stolz, den wir noch hatten, in die Wohnung der feindlichen Eltern zurückgekehrt. Natürlich nur, weil das Schaf so fror. Ich denke mal, mein Leben wäre ohne das Tier in eine andere Richtung gelaufen. In den Jahren später, wollte ich immer mal wieder alles hinschmeißen. Ein neues Leben anfangen, irgendwo, wo mich keiner kennt. Ich habe es nie getan. Weil mich immer dies verfluchte Schaf warnte, und eigentlich habe ich nur wegen ihm kein mutiges, verrücktes Leben geführt. Talismänner machen so etwas. Das ist ihre Bestimmung. Sie zwingen Menschenleben in die feige Bedeutungslosigkeit, weil sie ihren Besitzer immer an Situationen erinnern, in denen sich überlegtes Handeln bewährt hat. Oder sie verführen ihren Inhaber zu einer unangemessenen Waghalsigkeit. Weil sie ihn in falschem Schutz wiegen. Egal, immer gaukeln Fetische dem Besitzer etwas vor und verleiten ihn zur Verantwortungsabgabe. Sie bringen Millionen Menschen dazu, häßliche Stofftiere, blöde Ketten und ausgetretene Stiefel von einem Ort zum anderen zu schleppen, Jungfrauen zu opfern und Kriege zu führen. Und all die Fetische lachen leise über unsere Dummheit. Guckt mal die Menschen an, kichern sie, die schleppen uns rum, umtanzen uns und verehren uns nur, um sich einzureden, daß sie nicht allein sind, auf dieser Welt, und daß sie Glück haben. Und wissen doch nicht, daß sie immer allein sind und Glück eine Illusion ist. Das Schicksal läßt sich nicht durch Schafe bestechen, das ist die Wahrheit. Aber die will keiner sehen. Wir brauchen einen kleinen, eigenen Gott. Vielleicht um uns daran festzuhalten, auf der Reise durchs Leben, die uns angst macht. Weil wir nur das Ziel kennen, und nicht den Weg. Ich weiß es nicht. Schon wieder weiß ich etwas nicht. Vielleicht werde ich nie mehr was wissen und werde aus dem Grund nie was werden. Und das nur, weil ich nicht mehr an Fetische glauben mag. Weil ich mein Schaf weggegeben habe. Da war ich gestern, auf der Station. Ich wollte, weil mir langweilig war, das Schaf waschen gehen. Und es fiel mir aus der Hand. Und fiel vor die Füße eines kleinen Kindes. Mann, war das ein häßliches Kind. Verheult war es, der Rotz lief ihm aus der Nase, und das Kind sah schmutzig aus. Ein kleines schmutziges Kind, das nach einem kleinen, schmutzigen Leben ausschaute. Und der kleine Drecksack hob mein Schaf auf. Stand da, mit meinem alten, blinden Stofftier in der Hand und sah mich an. Zuckte zusammen, beim Treffen unserer Augen, als würde es einen Schlag erwarten. Das war so ein Reflex, daß ich mir dachte, das kennt es wohl, das Kind. Geschlagen werden und Menschen, die ihm etwas wegnehmen, das ihm Freude macht. Das Kind guckte so, und seine kleine dreckige Hand hielt mein Schaf so fest, daß die Hand ganz rot wurde, vor Anstrengung. Wir standen da ewige Sekunden. Dann faßte ich nach meinem Schaf. Strich ihm noch mal über die eingedellte Schnauze und ging weg. Weil ich erwachsen bin, ging ich weg, und weil ich doch inzwischen wissen sollte, daß Traurigkeit aufhört. Und daß einem Schafe wirklich nicht helfen.
Sibylle Berg – NORA verabschiedet sich (Auszug aus dem Roman “Ein paar Leute suchen das Glück und lachen sich tot”)
Message aus der Box
Viele Entwickler kennen und nutzen die Funktion MessageBox zur Ausgabe von Hinweistexten. Die wenigsten wissen aber, dass man ab Windows XP (frühere Systeme habe ich noch nicht getestet) den angezeigten Text auch mit Strg+C in die Zwischenablage kopieren kann.
Drückt man also während der Anzeige des folgenden Fensters
Strg+C oder Strg+Einfg, dann wird der Inhalt wie folgt in die Zwischenablage kopiert:
—————————
SpeedCommander
—————————
Beim Einfügen der Dateien aus der Zwischenablage ist ein Fehler aufgetreten.
—————————
OK
—————————
Benannte Threads
Beim Debuggen von Anwendungen mit mehreren Threads fragt man sich mitunter, in welchem Thread man sich gerade durch den Debugger quält. Im VC6 gibt es ein Thread-Fenster, was aber nur den Namen der Startfunktion des Threads bzw. den der aktuellen Funktion anzeigt. Sobald man mehrere Threads mit den gleichen Funktionen zu debuggen hat, wird die Unterscheidung schwer, welcher Thread gerade aktiv ist:
In der MSDN-Library ist beschrieben, wie man durch das Auslösen einer bestimmten Exception die einzelnen Threads benennen kann. Hierzu wird eine Struktur definiert, die u.a. mit dem Namen und der entsprechenden Thread-ID initialisiert wird. Anschließend wird eine Exception ausgelöst. Erfolgt der Aufruf zum Setzen des Namens für den aktuellen Thread, so kann als Thread-ID auch -1 übergeben werden. Zu beachten ist, dass der Name des Threads immer in Ansi (und nicht in Unicode) angegeben wird.
typedef struct tagTHREADNAME_INFO { DWORD dwType; // Typ (muss 0x1000 sein) LPCSTR pszName; // Zeiger auf den Threadnamen (ANSI) DWORD dwThreadID; // Thread-ID (-1 fuer den aufrufenden Thread) DWORD dwFlags; // Reserviert (muss 0 sein) } THREADNAME_INFO; void Lwl_SysSetThreadName(DWORD dwThreadID, LPCSTR pszThreadName) { // Struktur initialisieren THREADNAME_INFO info; info.dwType = 0x1000; info.pszName = pszThreadName; info.dwThreadID = dwThreadID; info.dwFlags = 0; // Exception generieren __try { RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD_PTR*) &info); } // __except(EXCEPTION_CONTINUE_EXECUTION) { } }
Beim Aufruf des Thread-Fensters werden die Threads nun mit ihrem Namen angezeigt. Beim VC6 besteht leider eine Limitierung des Namens auf 9 Zeichen, die im aktuellen Visual Studio 2003 aber aufgehoben ist.
Auch in der .netten Welt mag es manchmal hilfreich sein, wenn man mehrere Threads anhand des Namens unterscheiden kann. In der Regel präsentiert sich das Thread-Fenster wie folgt:
Der Threadname für den Debugger lässt sich direkt über die Eigenschaft Name der Thread-Klasse des .NET-Frameworks festlegen.
Public Class Form1 : Inherits Form Class Needle ' This method will be called when the thread is started Sub Baz() Console.WriteLine("Needle Baz is running on another thread") End Sub End Class Public Shared Sub Main() Console.WriteLine("Thread Simple Sample") Dim oNeedle As New Needle() ' Create a Thread object Dim oThread As New Thread(AddressOf oNeedle.Baz) ' Set the Thread name to "MainThread" oThread.Name = "MainThread" ' Starting the thread invokes the ThreadStart delegate oThread.Start() End Sub End Class
Der Zugriff auf das Thread-Objekt des Prozesses erfolgt über Thread.GetCurrentThread. Nach dem Setzen der Threadnamen sieht das Fenster im Debugger nun so aus: