Die Return-Taste in Dialogen, eine unendliche Geschichte

Wer sich in Windows-Programmierer-Foren tummelt, dem wird die folgende Frage mindestens einmal in der Woche über den Weg laufen:

Ich möchte in einem Dialog die Eingabetaste abfangen, so dass sich der Dialog nicht schließt.
Wie geht das?

Vom Sinn und Unsinn dieses Wunsches wollen wir mal hier nicht reden. Also schauen wir mal auf dieses Problem etwas genauer.

Die Lösung WM_COMMAND mit IDOK abfangen, lasse ich nicht gelten , denn evtl. hat der Dialog ja einen OK Button. (In der MFC käme das mit dem Überschreiben von CDialog::OnOK gleich)

Die Standardantwort von MFC Entwicklern lautet immer:

Überschreibe PreTranslateMessage und fange WM_KEYDOWN mit VK_RETURN dort ab und behandle die Nachricht dort.

OK. Aber was macht ein Win32-API Entwickler. Der hat keinen Einfluss auf die Message-Loop. Muss der nun zu der Hook-Kanone greifen?

Wo liegt eigentlich das Problem?
Die Funktion, die VK_RETURN in ein WM_COMMAND mit IDOK umwandelt ist die API Funktion IsDialogMessage. Diese Funktion wird in der Messageloop von modalen Dialogen verwendet. In der MFC direkt in CDialog::PreTranslateMessage und in Win32 Dialogen wird sie in der Messageloop die DialogBox ausführt.

Diese Funktion geht aber mit solchen Tastatureingaben nicht einfach wahllos um. Sie macht das sehr intelligent. Das sieht man schon daran, dass man in einer aufgeklappten ComboBox die Eingabetaste drücken kann und es schließt die ComboBox und nicht der Dialog.
Ebenfalls kennt man den Effekt, dass ein mehrzeiliges Eingabefeld (Edit Control) sehr wohl die Eingabetaste als Zeilenschaltung nutzen kann und auch hier schließt der Dialog nicht.
Kennt also IsDialogMessage seine Pappenheimer von Controls?

Woher weiß IsDialogMessage also wann VK_RETURN mal so und mal so behandelt werden muss?
Die Antwort ist einfach: IsDialogMessage fragt seine Controls ab was sie gerne hätten!
Und das wird erreicht mit einer einfachen Windows-Nachricht, die in diesem Zusammenhang fast nie genannt wird: WM_GETDLGCODE.

Bevor IsDialogMessage eine Tastatureingabe selbst behandelt, fragt die Funktion das Zielfenster mit WM_GETDLGCODE ab, ob das Fenster evtl. selbst Verwendung für diese Tastatureingabe hat.
WM_GETDLGCODE kann hier sehr flexibel reagieren, denn in wParam erhält die Nachricht den virtuellen Tastencode und das Control weiß nun sehr genau um was es hier geht.

Kann man also in jedem Control (das natürlich den Fokus hat) die Eingabetaste selbst behandeln?
Antwort: Ja!

Das Control muss nur einfach WM_GETDLGCODE behandeln für die Taste VK_RETURN. In diesem Fall gibt es DLGC_WANTMESSAGE zurück (ist übrigends identisch zu DLGC_WANTALLKEYS). IsDIalogMessage wird in diesem Fall die Eingabetaste an das Control weitergeben und nicht selbst behandeln. So macht es übrigens auch eine ComboBox die aufgeklappt ist wenn die Eingabetaste gedrückt wird.

Ich habe einfach mal hier eine Win32 Lösung aufgebaut. Es wird einfach das entsprechende Control mit einer eigenen WndProc gesubclassed und das sieht etwa so aus:

LRESULT CALLBACK SpecialWndProc(HWND hWnd,
                        UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_GETDLGCODE:
        if (wParam==VK_RETURN)
            return DLGC_WANTMESSAGE;
        break;
    case WM_KEYDOWN:
        if (wParam==VK_RETURN)
        {
            MessageBox(hWnd,_T("VK_RETURN received!"),NULL,MB_OK);
            return 0;
        }
        break;
    }
    WNDPROC pWndProc = reinterpret_cast(GetWindowLongPtr(hWnd,GWL_USERDATA));
    return CallWindowProc(pWndProc,hWnd,message,wParam,lParam);
}

...
// Subclass the control
HWND hWndEdit = GetDlgItem(hDlg,IDC_EDIT1);
LONG_PTR pOldWndProc = SetWindowLongPtr(hWndEdit,GWL_WNDPROC,
                        reinterpret_cast(SpecialWndProc));
SetWindowLongPtr(hWndEdit,GWL_USERDATA,pOldWndProc);

Effekt: Jedes Control, dass man mit dieser WndProc subclassed wird die Messagebox anzeigen und der Dialog wird nicht geschlossen! Subclassed man also alle Controls eines Dialoges, die den Fokus bekommen können, dann kann man sofort ohne Hook, auch die Eingabetaste selbst behandeln.
Sinnvollerweise sendet man bei Erhalt von VK_RETURN dann einfach eine entsprechende Nachricht an das Parent, dass die gewünschte Aktion dann ausführt, sofern nicht das Control selbst die Eingabetaste direkt behandelt.

Man kommt also auch in reinen Win32 API Programmen ganz ohne Hooks aus, um VK_RETURN in Dialogen so zu behandeln wie man es selbst gerne möchte!

Dialog basierende MFC-Anwendungen einmal anders

Ich habe mich zwar gerade erst darüber ausgelassen, dass man dialog basierende Anwendungen gar nicht braucht (sieh hier), aber ganz und gar unsinnig sind sie ja nicht.

Bei mir ist der häufigste Anwendungsfall eine simpler Dialog, der nur zum Steuern eines Programmes dient, das ein Icon in der Taskleiste abablegt.

Ärgerlich ist dann nur, dass man nicht vermeiden kann, dass beim Start der Anwendung der Dialog kurz aufflackert, bevor man ihn mit einem Timer oder mit PostMessage und einer benutzerdefinierten Nachricht wieder versteckt.

Aber es geht viel einfacher und dazu noch in einer Form, die der MFC-Konstruktion besser entspricht, als das Gerüst, das der Applikations-Wizard erzeugt.
Ich möchte das hier einfach kurz erläutern:

1. Erzeuge ich eine normale dialog basierende Anwendung mit dem Wizard.

2. Dann lege ich ein Objekt der Dialog Klasse mit dem Namen m_dlg in der CWinApp Klasse an. Ich möchte damit erreichen, dass die Dialogklasse so lange existiert wie die Applikation und nicht mehr nur als lokale Variable in InitInstance auftaucht.

3. Der gesamte Code, den der Wizard erzeugt hat:

CTestDLGDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
    // TODO: Place code here to handle when the dialog is
    // dismissed with OK
}
else if (nResponse == IDCANCEL)
{
    // TODO: Place code here to handle when the dialog is
    // dismissed with Cancel
}

wird ersetzt durch:

// Create the Dialog
if (m_dlg.Create(CMyDlg::IDD))
{
    m_pMainWnd = &m_dlg;
    return TRUE;
}
else
    return FALSE;

4. Kleine Schönheitskorrektur nun. Man verlagert das Laden des Applikations-Icons aus dem Konstruktor des Dialogs nach OnInitDialog.

m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);

Fertig ❗

Kurz erklärt: Aus dem modalen Dialog der durch DoModal gestartet wird wird nun ein modaler Dialog, der durch die Messageloop der CWinApp::Run gesteuert wird. Von der sonstigen Funktionsweise ändert sich nichts.
Dieser Konstrukt hält sich nach meiner Meinung weit mehr an das MFC Gerüst, als die Anwendung, die der Wizard erzeugt.
Die Vorteile sind schnell aufgezählt:

  1. InitInstance wird durchlaufen und CWinApp:Run wird verwendet.
    Dies hat z.B. zur Folge, dass auch CWinApp::OnIdle durchlaufen wird und temporäre Map Objekte entsorgt werden. (siehe Kommentar unten)
  2. Man kann die Applikation ohne Flackern zu dem Moment sichtbar machen an dem man es möchte. Dazu muss man nur das WS_VISIBLE Flag im Dialogtemplate entfernen und ShowWindow aufrufen, wenn es einem passt.

Vielleicht sehen ja andere Leser noch mehr Vorteile… die Diskussion ist eröffnet 😉

PS: Noch ein kleiner Nachtrag zu den oben erwähnten temporären Handle Maps in einer dialog basierenden Applikation. Diese temporären Handlemaps werden automatisch aufgeräumt und gelöscht wenn CWinApp::OnIdle ausgeührt wird. Das ist normalerweise der Fall wenn in CWinApp::Run keine Nachricht in der Messagequeue liegt, die abgearbeitet werden muss (PeekMessage gibt FALSE zurück). Im Gegensatz dazu werden Handlemaps nicht gelöscht wenn die Nachrichtenschleife mit CWnd::RunModalLoop für einen modalen Dialog ausgeführt wird. So also auch niemals in einer dialog basierenden Anwendung, oder wenn ein modaler Dialog in einer MFC-Applikation ausgeführt wird!
Das fast noch mal einen Artikel wert…

Manche ungenaue Dokumentation nervt einfach

Die MSDN ist eines der größten und besten Nachschlagewerke, die ich kenne, allerdings gehen mir auch seine Unzulänglichkeiten ab und zu auf die Nerven (und hier meine ich nicht die funktionellen Defizite, sondern die inhaltlichen)!

Das sind dann ganz besonders die kleinen Sachen, die einem das Leben leicht machen könnten, die dann nicht „vollständig“ dokumentiert sind.

Beispiel:
Die MFC List-Container (z.B. CObList). Wir lesen in der Doku zu InsertAfter:

position
A POSITION value returned by a previous GetNext, GetPrev, or Find member function call.

Gut! Aber da steht nichts zu NULL. 😕
Aber wenn ich genauer darüber nachdenken, kann GetNext und Find auch NULL zurückgeben…
Was passiert also wenn ich NULL als Argument für position?
Mein Verstand sagt mir: „Es wird ein Element am Ende eingeführt!“ und ein Blick in den Code bestätigt den Verdacht.

Warum steht in der Doku nicht gleich, dass InsertAfter mit NULL als position ein AddTail ausführt?
Wer weiß wieviele Entwicler solchen „unnützen“ Code aufgrund der mangelnden Doku geschrieben haben:

CStringList lst;
FillMyList(lst);
POSITION pos = lst.Find(_T("Anything"));
if (pos==NULL)
    lst.AddTail(_T("Something to insert"));
else
    lst.InsertAfter(pos,_T("Something to insert"));

Es würde ja genügen wie folgt zu schreiben:

CStringList lst;
FillMyList(lst);
POSITION pos = lst.Find(_T("Anything"));
lst.InsertAfter(pos,_T("Something to insert"));

Korrespondierend ist die Doku von CObjList::InsertBefore genauso unvollständig.
Wird NULL als Position verwendet wird hier ein AddHead ausgeführt.

PS: Mich ärgert auch jedesmal wenn einer meiner Kollegen solchen Code schreibt:

CSomeObject *p = NULL;
...
// Conditional create
if (SomeThing())
     p = new CSomeObject();
...
// Cleanup
if (p)		// Category meaningless
    delete p;
  1. Warum der Test auf !=NULL?
  2. Warum wird kein Autopointer verwendet, der das auch Exception-Save gemacht hätte?

Die Unsitte dialogbasierende Anwendungen zu bauen statt SDI mit CFormView zu verwenden

Es scheint mir meistens ein Anfängerfehler zu sein, dass viele MFC Entwickler (oder solche die es werden wollen) zuerst mal zu einer dialogbasierenden Anwendung greifen.
Ist ja auch nett. Man kümmert sich nur um die paar Dialogfelder und hat kein Doc/View zu verwalten.

Am Ende kommen aber dann noch weitere Wünsche:

  • Ich hätte gerne ein Menü
  • Ich hätte gernenoch einen Toolbar
  • Ich hätte gerne einen Status Bar
  • Schön wäre ein Accelerator

Triviales und nicht triviales schließt sich an:

  • Warum kann ich kein Command/Routing für meinen Toolbar und mein Menü verwenden?
  • Warum schließt mein Dialog bei Nutzung Eingabe-Taste?
  • Kann ich meinen Dialog auch resizen?
  • Die Daten sollen auch gespeichert werden, wie geht das?
  • Ich hätte gerne so einen schönen MFCNext Toolbar in meinem Dialog, oder ein Ribbon, geht das?
  • … (die Liste ist bestimmt nicht vollständig)

Gibt man dann die Antwort, dass man x Klimmzüge machen muss um so etwas in eine dialogbasierende Anwendung einzubauen (wenn es überhaupt geht), dann erntet man noch noch die stöhnende Klage: „Ohhh Mann! Ist das kompliziert!“

Und all das geht nur mit Mühe in einen CDialog einzubauen. Der Grund ist einfach: Das Commandrouting für all diese Elemente ist in CFrameWnd integriert. Aber ein CDialog leitet sich von CWnd ab und ist von Grunde auf für keine dieser Funktionen vorbereitet.

Dabei könnte alles so einfach sein!
Man muss nur einfach eine SDI Applikation mit einem CFormView erzeugen und alle die Wünsche die mancher später hat, kann man sofort erfüllen und wenn man es wirklich nicht will auch weglassen.

Die Frage stellt sich für mich also:
Warum nicht einfach immer gleich eine SDI/CFormView Anwendung bauen ❓
Das Potential dieser Anwendungsform ist einfach unerreichbar verglichen mit einer dialogbasierenden Anwendung.

Also sollte man sich mal die Liste der Wünsche, die ich hier aufgestellt habe ansehen und als Checkliste betrachten. Sollte einer dieser oben aufgeführten Punkte für die Applikation wichtig sein, würde ich dringend anraten zum SDI/CFormView zu greifen.
Das ist auch der Fall, wenn diese Anforderungen erst später integriert werden soll. Oft genug ist ja die dialogbasierende Anwendung schon fertig und es heißt dann: Ich möchte doch nur noch…

PS: Ich persönlich benutze nicht mal mehr zu Testzwecken dialogbasierende Anwendungen 😉

Dr. Watson starb unter Vista…

… und es war nicht etwa wieder Prof. Dr. Moriarty :mrgreen:

Wenn man unter Vista einen Crashdump benötigt dann sucht man DRWTSN32.EXE unter Vista vergeblich. Für die Qualitätssicherung sind mir Minidumps immer extrem wichtig.

Es stellt sich die nette Frage: Wie kommt man von einem Kunden dann an einen informativen Minidump, wenn man keinen WER-Account hat, oder das Programm nicht signiert war, oder gar der WER Server diese Dumps nicht anfordert?

Unter Vista ist alles noch einfacher und schwieriger geworden. Vista speichert leider nicht grundsätzlich Minidumps, aber es legt für Crashs unter Problemberichte und Lösungen einen eigenen Eintrag an. Zu Crashs von WER registrierten Programme werden hier evtl. direkt Lösungen oder Updates angeboten. Und auch Crashdumps werden hier mit abgelegt, wenn sie erzeugt werden..

So ist es unter Vista aus:

  1. Das WER System in Vista erzeugt normalerweise nur Minidumps für signierte Programme bzw. wenn der WER Server einen Crash abruft.
  2. Um immer einen Minidump zu erhalten muss in der Registry der folgende Wert gesetzt werden:
    HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\ (DWORD)  mit Namen ForceQueue  erzeugen und auf 1 setzen.
  3. Dumps werden im Benutzerverzeichnis unter C:\Users\TheUserName\AppData\Local\Temp und hier C:\ProgramData\Microsoft\Windows\WER\ReportQueue abgelegt und sind meistens gepackt. Sie erhalten die Endung *.mdmp.
  4. Der Zugriff kann ganz einfach erfolgen mit Systemsteuerung -> System und Wartung -> Problemberichte und Lösungen -> Probleme anzeigen und überprüfen
  5. Dort findet man das entsprechende Programm und die dazugehörigen entsprechenden Crashs.
  6. Weitere Infos einfach über Details anzeigen lassen.
  7. Wenn es einen Dump gibt, dann findet man hier wieder einen Eintrag Temporäre Kopie dieser Dateien anzeigen
  8. Damit lässt sich der Explorer öffnen und man erhält Zugriff auf die gespeicherten Daten.
  9. Diese kann man sich nun als Entwickler zusenden lassen.

Siehe auch:

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:Entzerren komplexer Pre- und Post-Build-Steps durch eigene Utility Projekte

Ich habe einige komplexe Projekte in die auch andere Tools eingebunden sind. Diese Tools erzeugen Dateien und Ressourcen erzeugen, die mit in den kompletten Build eines Projektes einfließen. Zum Teil sind es Daten, die auf einen speziellen Weg kompiliert werden oder auch Ressourcen, die durch ein sekundären anderen Compiler erst mit verarbeitet werden müssen.

Teilweise hatte ich diese Prozesse in einen Pre– und Postbuild Step eingebaut. Leider verhält sich Visual Studio oft genug eigentümlich, es merkt nicht das abhängige Dateien während des Build Prozesses verändert werden.

Bei einer Umstellung meiner Projekte stellte ich fest, dass es weitaus einfacher ist für solche speziellen Vorgänge eigene kleine Projekte zu erzeugen vom Typ „Utility„.

In diesen Projekten kann man einfach Ein- und Ausgabedateien definieren und den Befehl, der dazu notwendig ist die Daten eben zu bearbeiten.
Das Ganze wird dann in eine Master Solution kombiniert mit den entsprechenden Abhängigkeiten. Das hat vor allem auch den großen Vorteil, das auch die Projekte parallel im Build durchlaufen werden können.

Utility Projekte sind für manche Aufgaben weitaus besser geeignet als Pre– und Postbuild Steps. Vor allem werden durch die Verwendung von Utility Projekten die einzelnen Vorgänge entzerrt und verständlicher.

Acceleratoren in Dialogen für Felder ohne Prompt bzw. Static Control

Mit Alt+Buchstabe ein Feld in einem Dialog anspringen ist der Tastaturliebhaber gewöhnt.
Aber was mach man wenn man keinen Platz für ein Static Control hat vor dem entsprechenden Eingabefeld. Oder wenn solch ein Static gar nicht in das Design passt, oder gar eine Grafik enthalten soll.

Man könnte PreTanslateMessage überschreiben und mit Hooks Klimmzüge veranstalten. Aber es geht weitaus einfacher.

Man kann das Static Control an die korrekte Stelle in der Z-Order platzieren und dann einfach auf „Invisible“ setzen. Der Accelerator funktioniert trotzdem.
Nur Statics, die disabled sind werden als Acceleratoren ignoriert. Das Acceleratoren auch für nicht sichtbare Controls funktionieren habe ich bereits in diesem Artikel Button + Accelerator + ShowWindow(SW_HIDE) – EnableWindow(FALSE) = Falle erwähnt.

Anmerkung:
Damit ein User weiß das Acceleratoren für dieses Feld funktionieren sollte ein sichtbarer Hinweis im Handbuch existieren. Da aber wenige Menschen überhaupt Handücher verwenden :mrgreen: eignen sich hier Tooltips für den entsprechenden Hinweis.  Etwa so wie das VisualStudio macht mit Show shortcut keys in ScreenTips

VisualStudio 2008 SP1 wird ab dem 11.08.2008 auf MSDN zum Download bereitstehen

Die englische Version des Microsoft SQL Server 2008 wurde gestern am 06.08.2008 veröffentlicht.

Auf der Startseite der MSDN Subscriptions findet sich folgende hoch interessante Information:

SQL Server 2008 RTM Available for Download

English downloads are available now and additional languages will be added on a daily basis. Visual Studio 2008 users will need to download and install Service Pack 1 which will be available here after August 11, 2008.

Das heißt, dass wir ab Montagmorgen mit dem finalen SP1 für Visual Studio 2008 rechnen dürfen, zumindest in der englischen Version. Ich befürchte, dass die deutsche Version noch ein paar Tage mehr auf sich warten lässt.
Wie immer ein Grund nur mit den englischen Entwicklungswerkzeugen zu arbeiten :mrgreen:

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!