Archiv der Kategorie: C++

Goto Fail als Lehrstück

Während dem vergangenen Wochenende und dem Anfang dieser Woche wurde der Fehler in der Zertifikats-Überprüfung in Apples Code ausführlich diskutiert. Der Quellcode kann öffentlich angesehen werden und dass die fehlerhafte Codezeile dann auch noch „goto fail“ heisst hat natürlich viele ermutigt mit Häme nicht zu sparen.

Goto Fail

Die Code-Zeile, die das Problem verursachte (gelb hervorgehoben)

Eine Variante dieses Fehlers hatte ich auch als Punkt 6 in den 9 unbeliebtesten Entdeckungen in fremdem Code beschrieben.

Ob der Fehler durch ein automatisches Mergen von zwei Codefiles, unkonzentriertes arbeiten beim Code schreiben oder ein unbeabsichtigtes Kopieren der Codezeile entstanden ist kann nur anhand der Codefiles nicht erörtert werden. Gleichwohl darf man aus den Fehlern anderer lernen und so habe ich mir Gedanken gemacht, wie dieser Fehler hätte vermieden werden können.

Hier ein paar Punkte, die aus meiner Sicht das Risiko vermindert hätten:

  • Code Reviews
    Wenn Code Änderungen von einer zweiten Person überprüft werden ist die Gefahr kleiner, dass ein solcher Fehler hinein rutscht. Ausgeschlossen werden kann es natürlich trotzdem nicht, denn Menschen machen Fehler.
  • Warnungen einschalten und beachten
    Da ich die Umgebung nicht kenne, mit der OS X und iOS gebuildet wird kann ich nicht beurteilen, ob der Compiler hier Unterstützung geboten hätte. Aber viele Compiler geben ab einem bestimmten Warning-Level eine Warnung oder sogar einen Fehler aus, wenn Codeabschnitte vorhanden sind, die gar nie erreicht werden können. Dabei müssen dann aber auch die Warnungen entsprechend beachtet werden, nicht wie in Punkt 7 meiner unbeliebtesten Entdeckungen in fremdem Code.
  • Coding Guidelines
    Mit Coding Guidelines, die verlangen, dass jeder Block mit geschweiften Klammern eingefasst wird, hätte dieser Fehler vermieden werden können oder er wäre vermutlich eher aufgefallen. Auch ein fehlerhaftes Mergen mit diesem Resultat wäre vermutlich nicht passiert. Die Guidelines müssen aber auch wieder durch ein Code Review oder durch ein Tool überprüft und durchgesetzt werden.

Fehler passieren, und vielleicht versteckt sich in all den Codezeilen, die ich schon geschrieben habe ein ähnlicher Fehler. Wenn ein solcher Fehler öffentlich wird versuche ich deshalb lieber, daraus zu lernen als über den Verursacher zu spotten.

Mit .NET den Fuss wegschiessen und es nicht bemerken

Mit C war es einfach, sich selber in den Fuss zu schiessen. Durch C++ wurde es erschwert, aber nicht verhindert, und es hatte schwerwiegendere Konsequenzen.

Mit C# / .NET ist es noch schwieriger, sich selbst in den Fuss zu schiessen; und falls man es tut, merkt man es noch nicht einmal.

So habe ich heute ein Problem behoben, bei welchem der Speicherverbrauch einer Anwendung (eines Services, um genauer zu sein) stetig zunahm. Natürlich habe ich als erstes überprüft, ob irgend ein Array immer mehr Objekte beinhaltet. Aber erst, als ich mit einer Testversion des ANTS Profiler von Red Gate Software den Speicherverbrauch überprüft hatte, entdeckte ich das Problem:

Ich erzeugte ein Objekt, welches einen Timer besitzt. Die Referenz auf dieses Objekt speichere ich in einem Array. Wenn das Objekt nicht mehr benötigt wird, lösche ich das Objekt aus dem Array. Es existiert keine Referenz mehr auf dieses Objekt, somit kann der Garbage Collector das Objekt aus dem Speicher entfernen.

Oder auch nicht …

Da das Objekt einen Timer enthält und sich beim Timer mit einem Eventhandler angemeldet hat, referenzieren sich das Objekt und der Timer gegenseitig. Der Garbage Collector kann somit weder Timer noch Objekt löschen und sie schwirren bis in alle Ewigkeit (bzw. bis zum Programmende), losgelöst von allen anderen Objekten, im Speicher umher.

Solche oder ähnliche Probleme entdeckte man unter Visual C++ um einiges schneller, da die Entwicklungsumgebung nicht freigegebene Objekte reklamierte, wenn man die Anwendung im Debug Modus testete. Mit dem Garbage Collector ist diese Kontrollinstanz entfernt worden, was mich in diesem Fall einiges an Zeit gekostet hat.

Ich könnte mir vorstellen, dass noch in einigen .NET-Anwendungen solche Speicherfresser vorhanden sind. Wenn es normale Desktop-Anwendungen sind, wird dies auch nicht so bald auffallen, da diese Anwendungen normalerweise nicht über mehrere Tage laufen.

Es wäre schön, wenn man, falls man dies möchte, den Garbage Collector ausschalten könnte und den Speicher wieder selber kontrollieren könnte, da man nur so sicher sein kann, dass auch alle Objekte wieder gelöscht werden. Ob mein Wunsch in einer der nächsten .NET-Framework-Versionen wohl erfüllt wird? Wahrscheinlich eher nicht.

Somit werden wir wohl mit ungeladenen Waffen umherlaufen müssen und immer wieder kontrollieren, ob wir nicht ein Loch in unserem Fuss haben…

Workaround bei RAS via .NET unter Windows Server 2003

Für eine .NET-Anwendung musste ich via Modem eine Internet-Verbindung aufbauen. Dabei wird das Password Authentication Protocol (PAP) verwendet. Da in der Version 1.1 des .NET-Frameworks keine RAS-Unterstützung vorhenden ist, schrieb ich eine .NET-Wrapper-Klasse, die den Zugriff auf die benötigten Funktionen des RASAPI (rasapi32.dll) ermöglicht.
Die Anwendung funktionierte auf meinem Entwicklungssystem (Windows 2000 Server) ohne Probleme. Als die Anwendung dann auf dem Einsatzsystem unter Windows 2003 Server getestet wurde, klappte die Einwahl nicht. Untersuchungen mit dem PortMon von sysinternals ergaben, dass alles funktionierte, aber bei der Authentifizierung zwar der Benutzername, nicht aber das Passwort übermittelt wurde.
Verschiedene Versuche mit .NET und C++-Programmen zeigten dann, dass die Einwahl mittels RASAPI mit einem C++-Programm funktioniert, mit einem .NET-Programm das Passwort aber jedesmal fehlte.
Recherchen im Internet ergaben keine Lösung, nicht einmal ein vorheriges Auftreten dieses Problems konnte ich finden.
Um dieses Problem in den Griff zu bekommen, schrieb ich eine kleine DLL, welche die RASAPI-Aufrufe kapselt. Die Funktionen dieser DLL sehen wie folgt aus:


#define DLLEXPORT extern "C" __declspec(dllexport)
DLLEXPORT DWORD Dial(
    LPCSTR connectionName,
    LPCSTR number,
    LPCSTR username,
    LPCSTR password,
    LPVOID callback,
    LPHRASCONN hRas)
 {
    RASDIALEXTENSIONS rasDialExtensions;
    rasDialExtensions.dwSize = sizeof(RASDIALEXTENSIONS);
    rasDialExtensions.hwndParent = NULL;
    rasDialExtensions.reserved = 0;
    rasDialExtensions.dwfOptions = 0;
    RASDIALPARAMS rasDialParams;
    rasDialParams.dwSize = sizeof(RASDIALPARAMS);
    strcpy(rasDialParams.szPhoneNumber, number);
    strcpy(rasDialParams.szCallbackNumber, "");
    strcpy(rasDialParams.szDomain, "");
    strcpy(rasDialParams.szEntryName, connectionName);
    strcpy(rasDialParams.szUserName, username);
    strcpy(rasDialParams.szPassword, password);
    DWORD res = RasDial(&rasDialExtensions, 0, &rasDialParams, 1, callback, hRas);
    return res;
}
DLLEXPORT DWORD HangUp(HRASCONN hRas)
{
    return RasHangUp(hRas);
}
DLLEXPORT DWORD GetConnectStatus(
    HRASCONN hrasconn,
    LPRASCONNSTATUS lprasconnstatus)
{
    return RasGetConnectStatus(hrasconn, lprasconnstatus);
}

Die .NET-Anwendung ruft nun die Funktionen dieser DLL auf. Dadurch funktioniert die Einwahl nun auch unter Windows 2003 Server.

Erster Installer mit NSIS

Schon zu oft habe ich mich mit dem Install Shield herumgeärgert. Der Ärger reichte von der schwer zu begreifenden Logik in der IDE bis zu Abstürzen, nach denen man das Installer Projekt noch einmal von vorne beginnen darf.
Nachdem ich einen Installer, den ich noch mit einer älteren Version (7.x wenn ich mich nicht irre) erstellt hatte, in mit der Version 8 aktualisieren sollte, habe ich mit Install Shield abgeschlossen. Das alte Projekt konnte ich nicht öffnen, und wenn ich ein neues erstellen wollte, stürzte Install Shield regelmässig ab.
Ich habe dann NSIS (Nullsoft Scriptable Install System) heruntergeladen und den Installer damit erzeugt. Da man für NSIS Scripts erzeugt, welche die Installation beschreiben, muss man vor Abstürzen der Installer IDE keine Angst haben. Ausserdem weiss man auch, was wirklich im Installer vorgeht.
Mithilfe des Dependency Walkers kann man auch ohne teure Tools herausfinden, welche Komponenten und Libraries benötigt werden.
Falls ich in nächster Zeit dazukomme, möchte ich ein kleines Tutorial mit NSIS und Dependency Walker auf meiner Website veröffentlichen.