Refactoring mit Hilfe des Compilers kann eine tückische Sache werden

Wieder mal eine nette Falle: Implizite Konvertierungen und ein Refactoring-Versuch.

Folgende Methoden wurden in einer Klasse verwendet:

...
bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet, long lIdProject,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet,
            CDataConnection &dataConnection,
            CAgvipTableCoreData &coreData);
...

Die dritte Methode passte mir nicht von der Reihenfolge der Argumente. und ich änderte sie wie folgt um:

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            CDataConnection &dataConnection);

Ich habe mich nun einfach darauf verlassen, dass der Compiler mir alle entsprechenden Code Stellen schon anmeckern wird, an denen hier was nicht passt. Da ich noch einiges anderes an der Klasse geändert hatte, dauerte es noch eine Weile bis ich den nächsten Build angeworfen habe, und ehrlich gesagt, habe ich das Refactoring dieser Funktion vergessen.
Typischer Fall von: Zu viel auf einmal & Der Compiler macht einfach nicht was ich will 😉

Was passierte? Nichts ❗
Ich bekam keine Fehlermeldung zu dieser Änderung, denn CDataConnection hat eine implizite Konvertierung auf bool. Die Folge war, dass die erste Signatur der Funktion auch dieser Folge von Argumenten entsprach.

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);

Logisch, dass diese Funktion natürlich eine anderes Verhalten hatte und hier nicht mehr das passierte was ich eigentlich wollte.
Dämlicherweise rutschte diese Änderung auch noch durch die Tests und eine ganze Funktionsgruppe unserer Software wurde lahmgelegt und so ausgeliefert… Ein Bug, dazu noch von der Kategorie vermeidbar.
Was lernen wir:

  1. Es gibt keine fehlerfreie Software!
  2. Die kleinen Änderungen bringen die größten Fehler!
  3. Sich beim Refactoring auf den Compiler zu verlassen kann tückisch werden!

Mein Blog ist 2 Jahre alt geworden + Gesegnete Weihnachtszeit + Ein gutes neues Jahr

Im Mitte Dezember 2006 hatte ich mir endlich die Zeit genommen diesen Blog einzurichten. Ich habe mit einigen Providern experimentiert bin aber dann schnell zu einer eigenen WordPress Installation gekommen, die ich ja auch immer noch erfolgreich verwende.

Schnell sind diese 2 Jahre sind vergangen und insgesamt 267 Beiträge wurden geschrieben. Und immer noch steigen die Zugriffszahlen langsam, aber stetig an, wenn ich der WordPress Blog-Statistik glauben darf. Ich hatte nicht gedacht, so viele Leser zu erreichen. Aber diese Bestätigung ist auch gut, denn was nützt ein Blog ohne Leser 😉

Allen regelmäßigen Kommentatoren sei hier auch einmal Dank für die immer guten und konstruktiven Beiträge.

Ich wünsche allen eine gesegnete Weihnachtszeit und ein gutes neues Jahr!

PS: In meinem Sidebar habe ich jetzt auch eine zusätzliche Combobox eingebaut, die es erlaubt weiter zurück in die Vergangenheit zu gehen als nur die bisherigen 18 Monate.

Was ist falsch mit diesem Code? (1)

Ein Kollege hatte ein größeres Makro in VBScript geschrieben und ein interessantes Problem dabei entdeckt. Ich mache mal ein Ratespiel daraus 😉

Was ist faul mit diesem VBScript-Code?

On Error Resume Next
If MyFunction() Then
    MsgBox "MyFunction succedded"
Else
    MsgBox "MyFunction failed"
End If

MsgBox "... continue execution ..."

Function MyFunction
    MsgBox "Start MyFunction"
    On Error Goto 0
...
    MsgBox "End MyFunction"
    Test = true
End Function

Externe und interne COM Zugriffe unterscheiden

Ich benutze als interne Makrosprache gerne VBScript. D.h. ich hoste VBScript.
Ich biete dann bestimmte COM-Objekte (IDisptach) an, die es dem Nutzer erlauben mein  Programm anzupassen oder intern zu steuern.

Nun macht es aber unter Umständen einen gravierenden Unterschied, ob ein COM-Objekt von einem programminternen Nutzer, oder von extern angesprochen wird. Intern möchte ich zum Beispiel mehr  oder andere Funktionen erlauben als durch einen externen Zugriff von einem anderen Programm oder Skript.

Wie kann man aber unterscheiden von wo ein COM Zugriff erfolgt ❓

Netterweise hat COM die Funktion CoGetCallContext, die mir genau diese Info liefert. Liefert CoGetCallContext S_OK und damit einen Interfacezeiger auf IServerSecurity, dann wurde der COM Zugriff von einem externen Prozess abgesetzt. Wenn CoGetCallObject E_NOINTERFACE zurück gibt haben wir einen internen Aufruf aus dem eigenen Prozess heraus.

bool IsExternalComCall()
{
 // get context
 CComPtr<IServerSecurity> spSecurity;
 HRESULT hr = ::CoGetCallContext(IID_IServerSecurity,
                      reinterpret_cast<void**>(&spSecurity));
 return SUCCEEDED(hr);
}

Herzlichen Dank an René König für diesen Ansatz, den er mir in microsoft.public.de.vc gab.

WinMain mit argc und argv

Mit WinMain / wWinMain / _tWinMain bekommt man einen Zeiger auf Befehlszeile frei Haus mitgeliefert als lpCmdLine. Es ist nicht schwer Code u schreiben um diese Zeile zu interpretieren. Die MFC stellt dazu eine eigene Klasse mit dem Namen CCommandLineInfo. Manchmal wäre es aber auch einfach nur schön wie bei einem simplen C/C++-Konsolenprogramm argc und argv zur Verfügung zu haben, die an main / wmain / _tmain übergeben werden. Leider werden die aber an die Startfunktion nicht übergeben. (Diese Frage taucht auch nicht selten in den Communities auf).

Versteckt in der CRT gibt es argc und argv aber immer, egal ob Konsolen- oder UI-Programm. Der Startup-Code der CRT initialisiert diese Werte immer und sie stehen als globale Variablen unter den Namen __argc und __argv bzw. __argvw zur Verfügung. Man muss nur einen #include aud stdlib.h machen und schon stehen diese Werte zur Verfügung.
Ist das Programm ein Unicode Programm ist der Array __argvw initialisiert und __argv ist NULL. Ist das Programm ein MBCS Programm dann ist __argv gefüllt und __argvw ist NULL. Entsprechend ist __targv in tchar.h definiert. (Geändert nach Kommentar von Marcus Humann am 10.09.08)

Mit __argc / __argv / __argvw / __targv kann man nun ganz leicht die Befehlszeile frei verarbeiten, wie man es gewohnt ist, auch wenn man ein Windows-GUI Programm erstellt, oder die MFC benutzt.

Das ist zwar nicht dokumentiert, aber seit VC6 bis zur aktuellen Version VC-20008 hat sich nichts geändert.

Bookmark on MSDN auch auf meiner Seite

Social Bookmarking V1 wurde ja gerade veröffentlicht, wie Kay Giza berichtete.

In meinem Blog auf der rechten Seite unter dem Blog-Info habe ich mal gleich mal das neue MSDN Bookmark Widget eingebaut. Ich bin mal gespannt wer es benutzen wird.

Der Einbau ist einfach. Man fügt nur den nachfolgenden Skript Code auf seiner HTML Seiten ein.

<script src="http://Services.social.microsoft.com/widgets/bookmark.jss?
type=1&brand=Msdn&locale=1031" type="text/javascript"></script>

So ist das ganze auch auf dieser Tools Seite beschrieben.
Was leider nicht dokumentiert ist und ich nur durch herumprobieren hinbekommen habe ist die Bedeutung des Type Parameters. Der Type Parameter hat folgenden Einfluss auf die Darstellung des Links.

  • Mit Type=0 erhält man nur ein Icon
  • Mit Type=1 erhält man das Link Icon inkl. dem Text Bookmark on MSDN

Wie man sieht habe ich mich für Type=1 entschieden.

Aufflackern eines Konsolenfensters bei Nutzung system und _popen

Immer wieder kommt die Frage auf, warum sich ein Konsolenfenster kurz öffnet wenn man eine Windows Anwendung, wie z.B. Notepad mit den CRT Funktion system startet.

Die Antwort ist ganz einfach, dazu muss man einfach mal nicht einmal unbedingt einen Blick in die Sourcen werfen, denn es ist sogar richtig für system dokumentiert in der MSDN.

The system function passes command to the command interpreter, which executes the string as an operating-system command. system refers to the COMSPEC and PATH environment variables that locate the command-interpreter file (the file named CMD.EXE in Windows 2000 and later). If command is NULL, the function simply checks to see whether the command interpreter exists.

Also kurz und bündig: Es wird zwangsläufig immer ein Konsolenfenster geöffnet. Das verschwindet zwar sofort wieder, wenn man eine GUI Applikation startet, aber das kann man vermeiden indem man gleich ShellExecute verwendet.

Ganz anders sieht es mit der Doku bei _popen aus. _popen scheint sich anzubieten, um die Ausgaben eines Tools in eine Datei umzuleiten. Aber auch _popen nutzt wie system CMD.EXE /c (COMSPEC). Um das heraus zu bekommen muss man allerdings den Source Code der CRT zu Rate ziehen. Das bedeutet, dass auch bei Verwendung von _popen ein Flackern durch ein Konsolenfenster nicht ausbleibt.

Wie man es richtig macht, wenn man die stdin/stdout umleiten will findet man in den wohlbekannten Artikeln der MSDN http://support.microsoft.com/kb/190351 und http://msdn.microsoft.com/en-us/library/ms682499.aspx. Durch die Verwendung von CreateProcess lässt sich auch ganz vermeiden, dass der zweite Prozess angezeigt wird.

Was tun gegen Fehler in statischen Tabellen bzw. Variablen Deklarationen

Ich habe es immer wieder mit langen Listen von statischen Datenbeschreibungen zu tun. Das können Parserlisten, oder Sprachbeschreibungen, die baumartig vorliegen oder einfach nur simple Arrays sein.

In den meisten Fällen versuche ich solche Strukturen als konstante PODs (Plain Old Data) aufzubauen. D.h. also in einer Form:

  1. sodass kein weiterer Code notwendig ist um diese zu initialisieren (z.B. vermeinden von Konstruktoren/Destruktoren)
  2. dass die Daten sogar möglichst als const deklariert werden und so in einem Code Segment in Modulen geshared werden können.
  3. dass sie sich leicht aus einer „Art Datenbank“ in C/C++ Code erzeugen lassen.

Oft haben diese Datenstrukturen eine komplexe Struktur und auch einen Aufbau nach bestimmten Regeln, wie z.B. dass alle Daten eines Arrays sortiert sein müssen. Dadurch kann der Algorithmus, der diese Daten nutzt natürlich effektiver geschrieben werden, als wenn er auf einem unsortierten Array aufsetzt.

Eine Erfahrung, die ich heute weitergeben will, lautet nun:

Vertraue niemals darauf, dass die Daten in statischen konstanten Strukturen so vorliegen wie Du es glaubst… zumindest nicht in der Debug Version! 😉

Hintergrund für diese Erfahrung ist ein Bug, der sich genau aus einer Annahme einschlich, das statische Daten in einer bestimmten Regel vorliegen. In dem aktuellen Fall war es die bereits erwähnte Regel: „Der Array ist sortiert, nach einer ID“. Das Ganze war sogar am Anfang der besagten Struktur und im Code korrekt und ausführlich dokumentiert. Allerdings ist der Sourcecode dieser Tabelle mehrere 1000 Zeilen lang und ein Entwickler fügte nun einfach ein paar neue Einträge an das Ende der Liste an.
<ironiemode>Außerdem, wer liest schon interne Programmdokumentationen, wo wir alle (insb. der Autor und die geschätzten Leser dieses Artikels) doch fähig sind, den Sinn und Zweck von Code mit einem Blick zu erfassen?</ironiemode>

Die Folge war: „Nun ist der Array nicht mehr sortiert“ und die weitere Folge war, dass ein Algorithmus, der auf einer binären Suche basierte, nicht fand, was er finden sollte.

Dabei hätte Stück simpler Validierungscode, vermeiden können, dass dieser Code in Produktion ging:

class CSomeMightyDataHandler
{
public:
    CSomeMightyDataHandler();

    ...

private:
    // complex static data
    struct S_COMPLEXDATA
    {
        ...
    };
    static const S_COMPLEXDATA *m_pComplexData;

    // Validator for Debug mode
#ifdef _DEBUG
    static const bool m_bValidated;
    static bool ValidateData();
#endif
...
};

#ifdef _DEBUG
// Validation for debug code only!
const bool CSomeMightyDataHandler::m_bValidated = CSomeMightyDataHandler::ValidateData();

bool CSomeMightyDataHandler::ValidateData()
{
    // Do some validation and reset bResult on any failure
    bool bResult = true;

    ...

    ASSERT(bResult);
    return bResult;
}
#endif

Mit solch einem Code wäre sofort ein ASSERT nach der ersten inkorrekten Code Ergänzung geflogen.

PS: Folgende Annahmen stimmen:

  1. Der Programmierer, der den Code falsch hinzugefügt war ich selbst.
  2. Der Programmierer, der beim Design der Klasse keinen Validierungscode geschrieben hat war auch ich selbst.
  3. Ich hatte wirklich vergessen, wie dieser Algorithmus ursprünglich mal von mir geplant war (sprich, dass eine Sortierung beachtet werden muss).
  4. Ich kann mir in den Hintern beißen für soviel selbst gemachte Dummheit.

Tipps & Tricks: #import durch #include ersetzen

Nicht wenige verwenden, wie ich auch COM Komponenten, aus dem eigenen Haus oder von Fremdherstellern. Eingebunden werden diese COM-Komponenten oft genug über das #import Statement, das ja eine wirklich simple Integration erlaubt.

Lästig ist nur, dass diese Komponenten nicht auf allen Rechnern in den selben Verzeichnissen liegen. Das macht es nicht leicht Projekte und Entwicklungmaschinen so auszustatten, dass alle Projekte gleich zu kompilieren sind. Da fängt es schon an, dass ein Entwickler ein englisches OS (C:\Program Files), ein andere ein deutsches (C:\Programme) und der dritte Entwickler benutzt das Installationverzeichnis der zu entwickelnden Komponente (C:\Dev\Project\Bin).

Aus diesem Grund bin ich dazu übergangen das #import Statement nur einmal auszuführen, und die entstehenden .tlh und .tli Dateien direkt in das Projekt aufzunehmen.
Zu schnell? OK, also schrittweise:

  • Ich binde die Komponente also wie gewohnt per #import ein.
  • Die entstehenden .tlh und evtl. auch die .tli Dateien werden in das Projektverzeichnis kopiert und in das Projekt aufgenommen.
  • Das das #import Statement wird nun auskommentiert und statt dessen entsprechende #include Statements eingesetzt.
  • Nachdem man das auch korrekt mit entsprechenden Versionsangaben der Komponente dokumentiert hat und auch dieses Verfahren in die Projektbeschreibung aufgenommen hat ist man fertig!

Vor- und Nachteile:

  • Nicht nur, dass dieses Projekt unabhängig kompiliert werden kann. Das Kompilieren ist auch noch schneller, denn die (an sich) statischen .tlh und .tli Dateien werden nicht immer neu erzeugt.
  • Vorrausetzung ist hier sicherlich, dass es sich hier um Komponenten handelt, deren Interface sich nicht mehr verändert, und man muss bei einem Update der Komponente natürlich auch manuell die neuen .tli und .tlh Dateien einbauen.
  • Ein weiterer Vorteil ist auch, dass das Interface fest eingebunden wird, das auch für die Auslieferung festgelegt wird und keine alte/oder neuere Version zu Überraschungen führt.
  • Was auch zu dem genialen Verhalten führt, dass man aus dem Sourcecontrol System eine Software Version erzeugen kann, die noch mit einer älteren/abweichenden COM Komponente erzeugt wurde ohne diese auch noch mal installieren zu müssen!

In welcher Reihenfolge werden Ressourcen in unterschiedlichen Sprachen gefunden?

Immer wieder taucht die Frage auf, wie die Funktionen zum Laden von Ressourcen (LoadImage, LoadCursor, LoadMenu etc.) eigentlich arbeiten und entscheiden aus welcher LANGID-Sektion die Ressource geladen wird.

Die Dokumentation von FindResource – auf der alle diese Funktionen basieren – gibt leider keine Auskunft.

Wenn man jedoch etwas sucht wird man in der Dokumentation von Developing International Software fündig. Dort findet sich der folgende Abschnitt mit dem Titel Multiple Language Resources, in dem auch beschrieben wird wie FindResource arbeitet und wie eine Ressource gesucht wird:

If the FindResource and FindResourceEx functions do not find any resources that match the language ID’s primary language, they search for resources tagged as „language-neutral.“ This language ID is useful for resource elements such as icons or cursors that are identical for all languages. If a bitmap or an icon will differ for some languages, you can define one language-neutral bitmap as the default and specify language IDs for as many other customized bitmaps as required. For example, bidirectional applications might require bitmaps with right-to-left directionality. Because the FindResource and FindResourceEx functions always search for specific language IDs first, they will always find a bitmap tagged with that language ID before they find one tagged as language-neutral. The search algorithm they follow is summarized in the following list:

1. Primary language/sublanguage
2. Primary language
3. Language-neutral
4. English (skipped if primary language is English)
5. Any