Verzwickter Code

Beim Überarbeiten einer Klasse, die sich um die Minimierung in den Infobereich kümmert, habe ich in folgenden Code entdeckt:

// TaskSwitchWnd erstellen
BOOL CMfxMinimizeToTray::CreateTaskSwitchWnd()
{
    // Fensterklasse registrieren, falls noch nicht geschehen
    WNDCLASSEX wcTaskSwitch; ZeroMemory(&wcTaskSwitch, sizeof(wcTaskSwitch));
    if (!GetClassInfoEx(AfxGetInstanceHandle(), TASKSWITCHWNDCLASS, &wcTaskSwitch))
    {
        // Infos setzen
        wcTaskSwitch.cbSize = sizeof(wcTaskSwitch);
        wcTaskSwitch.style = CS_DBLCLKS | CS_BYTEALIGNWINDOW;
        wcTaskSwitch.lpfnWndProc = TaskSwitchWndProc;
        wcTaskSwitch.hInstance = AfxGetInstanceHandle();
        wcTaskSwitch.hIcon = (HICON) GetClassLongPtr(AfxGetMainWnd()->GetSafeHwnd(), GCLP_HICON);
        wcTaskSwitch.hIconSm = (HICON) GetClassLongPtr(AfxGetMainWnd()->GetSafeHwnd(), GCLP_HICONSM);
        wcTaskSwitch.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
        wcTaskSwitch.lpszClassName = TASKSWITCHWNDCLASS;
        wcTaskSwitch.cbWndExtra = sizeof(DWORD);

        // Fensterklasse registrieren
        VAPI(RegisterClassEx(&wcTaskSwitch));
    }

    // Fenster erstellen
    m_hWndTaskSwitch = CreateWindow(TASKSWITCHWNDCLASS, _T(""), WS_OVERLAPPEDWINDOW, 0, 0, 0, 0, m_hWnd, NULL, AfxGetInstanceHandle(), 0);
    if (NULL == m_hWndTaskSwitch)
        return FALSE;

    // Elternfenster setzen
    SetWindowLongPtr(m_hWndTaskSwitch, 0, (LONG_PTR) m_hWnd);

    // Fenstertext vom Hauptfenster
    TCHAR szWindowText[256]; GetWindowText(m_hWnd, szWindowText, countof(szWindowText));

    // Fenstertext setzen
    CString strTaskSwitch; strTaskSwitch.FormatMessage(IDS_MFX_TASKSWITCH, szWindowText);
    SetWindowText(m_hWndTaskSwitch, strTaskSwitch);

    //
    return TRUE;
}

Was stimmt damit nicht?

Updates sind kostenfrei

Immer wieder wird geschrieben, dass man bei SpeedCommander für jedes Update erneut zur Kasse gebeten wird. Dem ist natürlich nicht so. Alle Updates innerhalb der jeweiligen Hauptversion sind kostenfrei. Für SpeedCommander 11 gab es z.B. insgesamt vier Zwischenversionen mit teilweise umfangreichen Neuerungen (z.B. Vista-Kompatibilität und UAC) sowie fünf kleinere Bugfixes. Die Updatefrequenz liegt mehr oder weniger bei 100 Tagen.

Erst ein Upgrade auf eine neue Hauptversion ist mit weiteren Kosten verbunden. Neue Hauptversionen erschienen in der Vergangenheit alle 18 bis 24 Monate, im Moment scheint es sich auf 24 Monate einzupendeln. Registrierte Anwender können das Upgrade seit Jahren für 18,95 Euro (Freischaltkey) erwerben. Aber kein Anwender wird zum Upgrade gezwungen. Es spricht auch nichts dagegen, mit der älteren Version weiterzuarbeiten, wenn einem die Funktionalität ausreicht, und mal die eine oder andere Version auszulassen.

Von Zeit zu Zeit kommt auch mal die eine oder andere PC-Zeitschrift mit einer kostenlosen upgradeberechtigten Vollversion von SpeedCommander daher. Voraussetzung für das Upgrade auf die aktuelle Hauptversion ist lediglich eine Registrierung der Vollversion.

Bei Squeez hat sich übrigens gezeigt, dass die Möglichkeit von lebenslangen kostenfreien Upgrades bei der Kaufentscheidung nicht im Vordergrund steht. Im Vergleich zur Vorversion haben sich die Verkaufszahlen hier nach der Umstellung des Lizenzmodells nämlich nicht wesentlich verändert.

Analyseergebnisse

Ich hatte euch ja schon über die Möglichkeiten der statischen Codeanalyse berichtet. Mittlerweile habe ich die meisten Module und Anwendungen mit /analyze kompiliert und alle angezeigten Warnungen beseitigt. Das waren gar nicht mal so wenige. Ein Teil war auf meine Bequemlichkeit zurückzuführen, für kleine dynamische Speicherallokationen keine Prüfung auf einen möglichen NULL-Zeiger durchzuführen. Im Normalfall ist es zwar eher unwahrscheinlich, dass kleine Anforderungen nicht befriedigt werden können, aber das ist dem Codeanalyse-Tool völlig egal und es meckert den Fehler zurecht an.

Auch die Freigabe von mit new WCHAR[260] allokiertem Speicher durch delete wurde kritisiert, richtig wäre delete[]. Bei objektlosen Datentypen macht das zwar keinen Unterschied, bei Objekten hingegen (z.B. new CObject[20]) werden bei einem einfachen delete keine Konstruktoren mehr aufgerufen.

Bei der Zeile

if (NULL != pHeaderCtrl && GetKeyState(VK_LBUTTON > 0))

wurde die Warnung

warning C6326: Potenzieller Vergleich einer Konstanten mit einer anderen Konstanten.

ausgegeben. Da liegt das Codeanalyse-Tool vollkommen richtig, den eigentlich sollte es auch

if (NULL != pHeaderCtrl && GetKeyState(VK_LBUTTON) > 0)

heißen. Die folgenden Zeilen sollten prüfen, ob die Variablen vom Typ FILETIME mit Werten ungleich 0 belegt sind. Wenn ja, dann ist die Zeit gesetzt und kann für den Aufruf von SetFileTime verwendet werden.

// Zu setzende Zeiten
BOOL fCreationTime = pCreationTime->dwHighDateTime && pCreationTime->dwHighDateTime ? TRUE : FALSE;
BOOL fLastAccessTime = pLastAccessTime->dwHighDateTime && pLastAccessTime->dwHighDateTime ? TRUE : FALSE;
BOOL fLastWriteTime = pLastWriteTime->dwHighDateTime && pLastWriteTime->dwHighDateTime ? TRUE : FALSE;

Das Codeanalyse-Tool war damit aber nicht einverstanden und meldete

warning C6287: Redundanter Code: Die Teilausdrücke links und rechts sind identisch.

Völlig korrekt, denn eigentlich sollte auch dwLowDateTime auf 0 geprüft werden. Glücklicherweise hat dieser Fehler keine Auswirkungen, denn kaum jemand wird eine Datei auf die ersten sieben Minuten des 01.01.1601 datieren wollen. Aber gut, dass auch solche versteckten Fehler gefunden werden.

Speziell bei diversen Stringformatierungen im printf-Stil spielt das Codeanalyse-Tool seine Stärken aus. Bei diesem Codeschnippsel

CString strMessage;
strMessage.Format(L"Fehler: %s:%s", GetLastError(), L"Fehlermeldung");

würde zum Beispiel die Warnung

warning C6067: Der 2-Parameter im Aufruf von “ATL::CStringT::Format” muss die Adresse der Zeichenfolge sein.

ausgegeben werden. Das Gleiche gilt natürlich auch für alle anderen verwendbaren Datentypen. Das Codeanalyse-Tool erkennt auch, wenn für den Parameter %s bei Unicode-Kompilierung statt eines Unicode-Strings ein Ansi-String übergeben wird. Dank diesem Hinweis konnte ich nun auch endlich klären, warum bei der Schnellansicht von Programmdateien bei den Bound Import Descriptors nur chinesische Schriftzeichen ausgegeben werden.

Bei Aufrufen von CString::FormatMessage kommt das Analysetool aber leider etwas durcheinander. Die einzelnen Parameter im Formatierungsstring werden durch %1%99 gekennzeichnet. Für einen String-Parameter  kann man entweder %1 oder %1!s! schreiben, was das Codeanalyse-Tool aber nicht erkennt. Somit meldet es irrtümlicherweise immer, dass zuviele Parameter übergeben werden.

Auch bei einigen sicheren CRT-Stringfunktionen werden Fehler gemeldet, die keine sind. sscanf_s erfordert zum Beispiel, dass für Stringparameter neben dem Speicher für den String auch dessen Größe übergeben werden muss, damit es zu keinem Pufferüberlauf kommt. Das Codeanalyse-Tool will davon aber nichts wissen und bemängelt unablässig die “falsche” Parameterliste. Hier hilft es nur, die Warnungen vor dem Aufruf der Funktion per #pragma warning(disable: Fehlernummer) temporär zu deaktivieren. Nach der Funktion aber die Wieder-Aktivierung nicht vergessen.

Im nächsten Schritt werde ich jetzt auch meine eigenen Bibliotheken mit den entsprechenden Codeanalyse-Tags versehen. Das wird sich aber vermutlich über mehrere Monate hinziehen, da ich das auch gleich mit einem intensiven Codecheck verbinden werde. Im Laufe der letzten Jahre haben sich schon ein paar Zeilen angesammelt, die man heute sicher ganz anders (und besser) schreiben würde.

Betreutes Computing für ängstliche ältere Damen

Spiegel Online bringt es auf den Punkt:

Im Grunde sind Macs Rentnerrechner, betreutes Computing gewissermaßen. Keine Viren, kaum Systemcrashs, automatische Festplattenbereinigung – mit einem Apple zu arbeiten, ist wie Nordic Walking. Eine Idee langsamer als sonst und mit teuren Krücken, die dafür sorgen, dass man nicht umfällt.

Ein Arbeitstag an einem Windows-Rechner ist dagegen ein Tanz auf dem Vulkan, ein Ritt auf einem wilden, ungezähmten Bullen, mit der Wut des bösen Steve im Bauch.

Hier findet ihr den vollständigen Artikel.