Komfortabler Dateimanager mit vielen Funktionen

Entwicklung

Von SourceSafe zu Subversion

By Sven on 28.05.2008 - 10:00 in Tools & Bibliotheken with 9 Kommentare

Seit mehr als 11 Jahren nutze ich Visual SourceSafe als Quellcodeverwaltung, letzte Woche habe ich meine Projekte auf Subversion umgestellt. Als SourceSafe-Geschädigter muss man sich aber erst einmal an das neue Modell gewöhnen. Unter SourceSafe checkt man die zu bearbeitenden Dateien einzeln oder in Gruppen aus, editiert sie und checkt sie dann wieder ein. Eingecheckte Dateien werden mit einem Schreibschutz-Attribut versehen und können daher nicht einfach so bearbeitet werden. Damit das Auschecken funktioniert, muss Visual Studio eine ständige Verbindung zum Server mit der Quellcodeverwaltung haben. Arbeitet man mal unterwegs, dann simuliert Visual Studio ein Auschecken, indem es das Schreibschutz-Attribut der lokalen Datei entfernt. Beim nächsten Andocken an den Server werden die Dateien dann richtig ausgescheckt.

Mit Subversion ist das alles ein wenig anders. Nach dem Abruf einer Arbeitskopie vom Subversion-Server kann man Dateien beliebig editieren. Sind die Änderungen abgeschlossen, werden die Dateien wieder auf den Subversion-Server übertragen. Dieser speichert dann nur die Unterschiede und hält so seine Datenbank möglichst klein. Eine Verbindung zum Subversion-Server muss nur während des Abrufens/Aktualisierens der Arbeitskopie und des Übertragens zurück bestehen. Damit lässt es sich auch problemlos unterwegs arbeiten.

Die Unterstützung von mehreren Entwicklungszweigen (Branches) ist in SourceSafe quasi gar nicht vorhanden. Zudem wird die Verbindung eines Projekts zur Quellcodeverwaltung direkt in der Projektdatei gespeichert, was die Ablage von Projektkopien in anderen Ordnern (z.B. bei einem neuen Release) sehr erschwert. Wenn man dann die Verbindung nicht löst, möchte Visual Studio immer die Verbindung zum Originalprojekt herstellen.

Mit Subversion gibt es diese Probleme nicht. Verschiedene Entwicklungszweige sind einfach zu verwalten, auch die Übernahme von Änderungen in andere Zweige ist problemlos möglich. Die Projektdateien sind anderen Dateien gleichgestellt und enthalten keine datenbankspezifischen Informationen mehr.

Das ermöglicht es mir jetzt endlich, ein paar Wochen vor der Veröffentlichung eines Updates einen separaten Zweig dafür zu erstellen, die aktive Entwicklung dafür einzustellen und wirklich nur noch nötige Korrekturen vorzunehmen. Die normale Entwicklung an der nächsten Version kann dann schon weitergehen, ohne auf das geplante Update Einfluss zu nehmen.  Damit entfällt nun auch die Wohlverhaltensperiode vor und nach dem Release, in der ich mich zwingen musste, meine Finger im Zaum zu halten. Treten nach dem Update doch noch unerwartete Fehler auf, so können diese im separaten Zweig behoben und schneller als Fix zur Verfügung gestellt werden.

Die Einrichtung von Subversion ist sehr einfach. Nach der Installation wechselt man in der Kommandozeile in das bin-Verzeichnis und richtet svnserver.exe als Dienst ein:

sc create svnserve binPath= “C:\Programme\Subversion\bin\svnserve.exe –service -r D:\Projekte\Subversion” DisplayName= “Subversion” depend= tcpip start= auto

Anschließend muss nur noch ein neues Projektarchiv erstellt werden. Alternativ kann man sich auch das Rundum-Sorglos-Paket VisualSVN Server herunterladen. Dieses installiert einen Apache-Server samt Subversion-Integration und erstellt auch gleich das Projektarchiv. Damit hatte ich meine ersten Gehversuche gemacht, bin aber anschließend auf die performantere Dienst-Methode umgestiegen.

Auf dem Client-Rechner wird TortoiseSVN installiert, damit erfolgt der Zugriff auf die Quellcodeverwaltung bequem aus SpeedCommander (oder dem Explorer) heraus. Für die Integration in Visual Studio empfiehlt sich VisualSVN. Die Einzellizenz kostet $49 und macht sich schnell bezahlt.

Letztlich kann ich jedem SourceSafe-Anwender nur empfehlen, auf Subversion zu wechseln. Beim Einstieg beantwortet die umfangreiche Dokumentation viele Fragen, für die ersten Gehversuche kann man sich ein Projektarchiv zum Spielen einrichten. Mit einer möglichen Konvertierung der SourceSafe-Datenbank habe ich mich nicht beschäftigt, ich wollte einen frischen Start ohne Altlasten.

SysLink-Control unter Windows 2000

By Sven on 18.04.2008 - 10:00 in Windows SDK with 1 Kommentar

Mit Hilfe des SysLink-Controls lassen sich anklickbare Hypertext-Elemente in einem Fenster darstellen. Die Formatierung des Textes erfolgt mit Hilfe von <a>anklickbarer Text</a>-Tags. Bei Aktivierung durch Maus oder Tastatur wird das Elternfenster informiert und es kann die jeweilige Aktion durchführen. Leider ist das SysLink-Control erst ab der ComCtl32.dll V6 enthalten und lässt sich so nur ab Windows XP verwenden.

Unter Windows 2000 gibt es ebenfalls ein SysLink-Control. Es ist aber nicht Bestandteil der ComCtl32.dll, die Implementierung erfolgt in der Shell32.dll. Mit Hilfe der Funktion LinkWindow_RegisterClass lässt sich das Control registrieren und verwenden. Der hauptsächliche Unterschied zum SysLink-Control von Windows XP ist der Name der Fensterklasse. Unter Windows XP heißt das Control SysLink, unter Windows 2000 Link Window. Zudem unterstützt die Windows 2000-Implementierung keine HREF und ID-Attribute innerhalb des Anchor-Tags.

Die verschiedenen Fensterklassen-Namen erschweren die Verwendung von SysLink-Controls in Dialogvorlagen. Der Dialog kann nur erzeugt werden, wenn alle verwendeten Fensterklassen registriert sind. Der Dialogeditor von Visual Studio verwendet SysLink als Fensterklasse, das verhindert aber die Anzeige des Dialogs unter Windows 2000.

Mit einem kleinen Trick lässt sich das Problem recht einfach lösen. Beim Start der Anwendung wird geprüft, ob die Fensterklasse SysLink existiert. Wenn nicht, dann wird mit Hilfe von LinkWindow_RegisterClass die Fensterklasse Link Window registriert. Anschließend ruft man die Fensterklassen-Daten für Link Window ab, ändert den Namen auf SysLink und registriert die Klasse unter dem neuen Namen. Nun kann man auch unter Windows 2000 mit SysLink arbeiten.

Das ganze sieht dann so aus:

BOOL RegisterCommonControls()
{
    // Common Controls initialisieren
    INITCOMMONCONTROLSEX initCCex;
    initCCex.dwSize = sizeof(initCCex);
    initCCex.dwICC = ICC_WIN95_CLASSES | ICC_LINK_CLASS;

    // Common Controls initialisieren
    InitCommonControlsEx(&initCCex);

    // Erfolg?
    BOOL fSuccess = FALSE;

    // Fensterklasse abrufen
    WNDCLASSW wndLinkClass;
    if (!GetClassInfoW(NULL, L"SysLink", &wndLinkClass))
    {
        // Registrierungsfunktion
        typedef BOOL (WINAPI* pfnLinkWindowRegisterProc)();

        // Shell32-Dll laden
        HINSTANCE hModShell32 = LoadLibraryW(L"shell32.dll");
        if (NULL != hModShell32)
        {
            // Funkion importieren
            pfnLinkWindowRegisterProc pfnLinkWindowRegister = reinterpret_cast<pfnLinkWindowRegisterProc>(GetProcAddress(hModShell32, MAKEINTRESOURCEA(258)));
            if (NULL != pfnLinkWindowRegister)
            {
                fSuccess = pfnLinkWindowRegister();
            }

            // Dll freigeben
            FreeLibrary(hModShell32);
        }

        // Fensterklasse fuer "Link Window" registriert
        if (fSuccess)
        {
            // Fensterklasse abrufen
            if (GetClassInfoW(NULL, L"Link Window", &wndLinkClass))
            {
                // Fensterklasse auf SysLink setzen
                wndLinkClass.lpszClassName = L"SysLink";

                // Fensterklasse registrieren
                if (!RegisterClassW(&wndLinkClass))
                {
                    fSuccess = FALSE;
                }
            }

            // Fehler beim Abruf der Fensterklasse
            else
            {
                fSuccess = FALSE;
            }
        }
    }

    // Fensterklasse existiert bereits
    else
    {
        fSuccess = TRUE;
    }

    // Erfolg
    return fSuccess;
}

Verzwickter Code – Auflösung

By Sven on 02.04.2008 - 10:00 in Entwicklung with Keine Kommentare

Die Antwort von Marcus zum verzwickten Code war richtig. In Zeile 17 wird für die Fensterklasse zusätzlicher Speicher angefordert, um in Zeile 29 das Handle zum Elternfenster zu speichern. Unter Win32 funktioniert auch alles wie gewünscht.

Es wird aber nicht beachtet, dass unter Win64 ein Fensterhandle nicht mehr vier Byte groß ist (DWORD), sondern acht Byte. Somit ist in der Fensterklasse nicht mehr genug Platz, um das Fensterhandle zu speichern. SetWindowLongPtr setzt daher den Fehlercode ERROR_INVALID_INDEX.

Zeile 17 muss also korrigiert werden zu

        wcTaskSwitch.cbWndExtra = sizeof(HWND);

Letztlich ist die Speicherung des Elternfensters aber auch total überflüssig, denn dieses lässt sich jederzeit über GetParent abrufen. Aber warum einfach, wenn es auch kompliziert geht.

Durch diesen Fehler ist es nicht möglich, den in den Infobereich minimierten SpeedCommander in der x64-Version per Tastatur wieder zu aktivieren. Bisher war dies noch keinem aufgefallen.

Verzwickter Code

By Sven on 31.03.2008 - 10:00 in Entwicklung with 6 Kommentare

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?

Analyseergebnisse

By Sven on 17.03.2008 - 16:54 in Entwicklung with 3 Kommentare

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.

Carbonite Backup ist etwas wählerisch

By Sven on 10.12.2007 - 11:42 in Entwicklung with 11 Kommentare

Ein SpeedCommander-Anwender setzt die Software Carbonite Backup ein, mit der die zu sichernden Daten über das Internet in ein Backup-Center übertragen werden. Mit Hilfe von Overlay-Symbolen wird der Status gekennzeichnet. Ein grüner Punkt zeigt z.B. an, dass die Datei gesichert wurde. Mit einem gelben Punkt versehene Dateien werden gerade gesichert.

Soweit so gut, doch leider werden die Punkte nur im Explorer angezeigt, in SpeedCommander sind alle Dateien punktlos. Eigentlich gibt es dafür aber keinen Grund, da SpeedCommander die entsprechenden Overlay-Symbole ebenfalls bei der Shell anfragt und dann auch anzeigen sollte.

Im Debugger sah ich dann, dass beim Aufruf von IShellIconOverlay::GetOverlayIndex der Fehlercode E_FAIL zurückgegeben wird. Laut Dokumentation bedeutet dies, dass die übergebene PIDL ungültig ist. Zu erklären ist dies aber nicht, da alle anderen Overlay-Symbole ja korrekt ermittelt werden.

Aus Verzweiflung kam ich auf die dumme Idee, SpeedCommander.exe in Explorer.exe umzubenennen und aufzurufen. Danach sah ich das:

SpeedCommander als explorer.exe

Die Darstellung der Overlay-Symbole von Carbonite Backup funktioniert also wohl nur in Prozessen, die Explorer.exe heißen. Zur Überprüfung habe ich mir dann die Datei CarboniteNSE.dll angeschaut und bin auf folgende hartkodierte Namen gestoßen:

  • explorer.exe
  • dopus.exe
  • pdexplo.exe
  • regsvr32.exe
  • verclsid.exe
  • carboniteui.exe

Ändere ich SpeedCommander.exe in einen dieser Namen, dann werden die Symbole korrekt angezeigt. Bei allen anderen verweigert Carbonite Backup die Anzeige der Symbole und des Kontextmenü-Eintrags.

Nichts ist unmöglich

By Sven on 31.10.2007 - 12:02 in Entwicklung with Keine Kommentare

Unter Windows x64 haben 64-bit Anwendungen und 32-bit Anwendungen jeweils ihren eigenen Tanzbereich. Diese sind strikt voneinander getrennt, ein Übergreifen in den des jeweils anderen ist nicht möglich. Mit ‘nicht möglich’ ist aber nur die Inprozess-Kommunikation mit LoadLibrary gemeint. Wenn man hingegen einen externen COM-Server als Vermittler einbezieht, dann sieht die Sache schon wieder ganz anders aus.

Der x64-SC nutzt diese Möglichkeit von Anfang an, da die für die Anzeige von Grafiken verwendete Bibliothek nur als 32-bit Dll vorliegt. Die Funktionen der Dll werden daher von einem kleinen 32-bit Programm aufgerufen, die Kommunikation zwischen dem x64-SC und dem kleinen 32-bit Programm erfolgt über COM. Mit den entsprechenden Informationen sorgt COM dafür, dass der Datenaustausch korrekt erfolgt. Solange die auszutauschenden Daten nicht zu komplex sind, ist dies auch eine recht übersichtliche Angelegenheit.

Nach dem gleichen Prinzip habe ich jetzt auch eine WfxWrapper-Implementation für den x64-SC geschrieben. Mit Hilfe des WfxWrappers können WFX-Plugins für den Total Commander auch im SpeedCommander verwendet werden. Bisher galt dies nur für den 32-bit SC, ab sofort funktioniert es auch mit dem 64-bit SC:

WfxWrapper im x64-SC

50% Rabatt

By Sven on 30.10.2007 - 11:47 in Tools & Bibliotheken with Keine Kommentare

Ich habe ja schon öfters erwähnt, dass ich für diverse UI-Elemente in SpeedCommander und Squeez (Menüs, Symbolleisten, Property Grid, Docking Panes, Task Panel) schon seit mehreren Jahren das Xtreme Toolkit von Codejock verwende. Alles in allem hervorragend geschriebener Code, der gut funktioniert und einem viel Arbeit abnimmt.

Erwähnenswert ist, dass es noch bis morgen (31.10.2007) 50% Rabatt auf alle MFC-Komponenten gibt. Statt $595 kostet die von mir verwendete All Inclusive-Version im Moment nur $298, die Anschlussfinanzierung für ein weiteres Jahr Updates ist für $98 erhältlich (statt $195). Letztlich ein gutes Timing von Codejock, meine Subscription läuft nämlich am Samstag aus.

Wer also Interesse an guten UI-Komponenten für die MFC hat, der sollte sich schnell entscheiden. Es lohnt sich.

Die Sache mit dem leeren Kontextmenü

By Sven on 28.01.2007 - 19:47 in Entwicklung with 11 Kommentare

Im Forum haben Anwender berichtet, dass SpeedCommander neuerdings nach einer gewissen Zeit nur noch ein (fast) leeres Kontextmenü anzeigt. Auch andere Funktionen, wie z.B. Drag & Drop, die Berechnung von Ordnergrößen oder die Anzeige des Dateiverknüpfungsdialogs stellen ihre Arbeit ein. Mit Hilfe des bewährten ShellExView hat akim schließlich herausgefunden, dass die Ursache die Kontextmenü-Erweiterung von Nero CoverEditor ist. Nach Deaktivierung dieser Erweiterung funktioniert alles wieder wie gewünscht.

Kontextmenü-Erweiterungen sind ganz normale Dlls, die bei der Zusammenstellung des Kontextmenüs durch die Shell automatisch geladen und in den Adressraum der jeweiligen Anwendung eingeblendet werden. Die Anwendung selbst hat keinen Einfluss auf die Erstellung des Kontextmenüs sowie darauf, welche Erweiterungen geladen werden. Fehlerhaft programmierte Kontextmenü-Erweiterungen können eine Anwendung damit sofort in den Abgrund reißen. Wesentlich tückischer sind aber Fehler, die beim Entladen der Dll auftreten. In der Regel werden Kontextmenü-Erweiterungen nur mit dem Explorer getestet. Da der Explorer aber hauptsächlich erst mit dem Herunterfahren von Windows beendet wird und die Dlls während der Laufzeit praktisch nie entladen werden, fallen fehlerhafte Kontextmenü-Erweiterungen hier nicht weiter auf.

Die Symptome beim oben geschilderten Fall lassen darauf schließen, dass die Kontextmenü-Erweiterung vom Nero CoverEditor anscheinend die OLE- bzw. COM-Funktionalität deaktiviert. Alle nicht mehr vorhandenen Funktionen basieren auf OLE bzw. COM. Beim Betrachten der exportierten Funktionen von CoverEdExtension.dll mit der Schnellansicht in SpeedCommander fällt gleich auf, dass die Bibliothek nur die Funktion CoUninitialize verwendet. Der Verweis auf ein dazugehörige CoInitialize fehlt dagegen:

OLE-Exporte von CoverEdExtension.dll

CoInitialize und CoUninitialize werden immer als Sandwich verwendet. Vor der Verwendung von COM-Funktionen in einem Thread muss mit CoInitialize immer erst eine Anmeldung an das COM-System erfolgen. Vor dem Beenden des Threads meldet sich dieser mit CoUninitialize wieder ab. Wichtig ist, dass es zu einem CoInitialize immer ein passendes CoUninitialize gibt. COM benutzt intern einen Referenzzähler, so dass innerhalb eines Threads CoInitialize und CoUninitialize auch mehrfach aufgerufen werden können. Erst wenn der Referenzzähler 0 erreicht, wird die Abmeldung vom COM-System endgültig durchgeführt.

Um zu prüfen, ob sich meine Vermutung auch in der Praxis bestätigt, habe ich einen Haltepunkt auf CoUninitialize gesetzt und SpeedCommander im Debugger gestartet. Überraschend war übrigens, wie oft CoUninitialize auch von der Shell aufgerufen wurde, allerdings ging hier wohl immer ein vorheriges CoInitialize voraus. Mit dem Rechtsklick auf eine Nero CoverEditor-Datei (.ncd) wurde auch die CoverEdExtension.dll geladen. Ein paar Sekunden später wurde wieder der Haltepunkt bei CoUninitialize aufgerufen. Im Aufrufstack war eindeutig zu erkennen, dass der Aufruf aus CoverEdExtension.dll kam und während des Entladens einer Dll erfolgte. In einem weiteren Test konnte ich durch einen Haltepunkt auf FreeLibrary erkennen, dass hier das Entladen der CoverEdExtension.dll erfolgte:

OLE32! CoUninitialize@0 address 0x4fedd362
COVEREDEXTENSION! 054c9a71()
NTDLL! LdrpCallInitRoutine@16 + 20 bytes
NTDLL! LdrUnloadDll@4 + 1002 bytes
KERNEL32! FreeLibrary@4 + 21 bytes
OLE32! CClassCache::CDllPathEntry::CFinishObject::Finish(void) + 33 bytes
OLE32! CClassCache::CFinishComposite::Finish(void) + 183736 bytes
OLE32! CClassCache::FreeUnused(unsigned long) + 126 bytes
OLE32! CoFreeUnusedLibrariesEx@8 + 44 bytes
OLE32! CoFreeUnusedLibraries@0 + 9 bytes
AfxOleTermOrFreeLib(int 0, int 0) line 151
AfxUnlockTempMaps(int 1) line 42
CWinThread::OnIdle(long 1) line 712
CWinApp::OnIdle(long 1) line 1012 + 12 bytes
CWinThread::Run() line 617 + 30 bytes
CWinApp::Run() line 898
...

Doch warum ist in diesem Fall nur SpeedCommander so anfällig und nicht auch alle anderen Dateimanager? Der Grund liegt in der Architektur der MFC, auf die SpeedCommander aufgebaut ist. Von Zeit zu Zeit werden in Phasen, in denen die Anwendung nichts zu tun hat und auf Benutzereingaben wartet, nicht mehr benötigte COM-Dlls mit Hilfe der Funktion CoFreeUnusedLibraries freigegeben. Das trifft in diesem Fall auch die CoverEdExtension.dll, die vor dem Entladen leider noch CoUninitialize aufruft. Da aber zuvor kein passendes CoInitialize erfolgte, erreicht der interne Referenzzähler vom COM-System 0 und COM wird für den aktuellen Thread abgemeldet. Der aktuelle Thread ist aber der Hauptthread von SpeedCommander und somit können ab diesem Zeitpunkt keine COM-Funktionen mehr aufgerufen werden.

Wer von diesem Problem betroffen ist, der kann sich also nur damit helfen, die Kontextmenü-Erweiterung von Nero CoverEditor zu deaktivieren. Möglicherweise behebt Nero das Problem auch mit dem nächsten Update.

IID_PPV_ARGS

By Sven on 08.12.2006 - 17:50 in Windows SDK with Keine Kommentare

Beim Stöbern in den Beispielen zum neuen Platform SDK ist mir das Makro IID_PPV_ARGS aufgefallen, welches häufig bei QueryInterface– und CoCreateInstance-Aufrufen benutzt wird. Die Definition des Makros ist in der Headerdatei objbase.h zu finden und schaut wie folgt aus:

//  IID_PPV_ARGS(ppType)
//      ppType is the variable of type IType that will be filled
//
//      RESULTS in:  IID_IType, ppvType
//      will create a compiler error if wrong level of indirection is used.
//
extern "C++"
{
    template<typename T> void** IID_PPV_ARGS_Helper(T** pp) 
    {
        // make sure everyone derives from IUnknown
        static_cast<IUnknown*>(*pp);
        
        return reinterpret_cast<void**>(pp);
    }
}

#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)

Das Makro stellt sicher, dass QueryInterface und CoCreateInstance immer die zum angeforderten Objekt passende Schnittstellen-IID übergeben bekommen.

Anstatt

...
IShellLinkPtr pShellLink = NULL;
if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &pShellLink)))
    return FALSE;
...

schreibt man daher besser

...
IShellLinkPtr pShellLink = NULL;
if (FAILED(CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pShellLink))))
    return FALSE;
...

und verhindert so schon mögliche Fehler während des Quellcode-Schreibens.

Top