Komfortabler Dateimanager mit vielen Funktionen

Entwicklung

Abenteuer x64

By Sven on 11.04.2005 - 22:32 in Entwicklung with 2 Kommentare

In den letzten Tagen habe ich mich damit beschäftigt, die Packer-Bibliotheken auf 64 Bit anzupassen. Das Kompilieren unter x64 geschah recht problemlos, allerdings stürzten einige der Bibliotheken (insbesondere die LZ77-Abkömmlinge) beim Komprimieren bzw. beim Entpacken ab.

Die Hauptursache war hier der Umgang mit Zeigern. Das Zeigermanagement kann sich unter x64 zu einer kniffligen Sache entwickeln, wenn man Zeigerarithmetik mit vorzeichenlosen Zahlen betreibt. Unter x32 gibt es hiermit keine Probleme, da es bei einem eventuellen Überlauf automatisch zu einer negativen Zahl kommt. Bei einer Erweiterung der Zeiger auf 64 Bit gibt es aber bei Operationen mit 32 Bit-Datentypen keinen Überlauf mehr, stattdessen wird das niedrigste Bit in den oberen 32 Bit gesetzt.

An einem Beispiel lässt sich das sehr gut demonstrieren. Der Ausgangspunkt ist ein kleines Stück C++-Code, der ein dynamisches Array erstellt und innerhalb dieses Arrays mit Zeigern arbeitet.

BYTE* pMem1 = NULL; BYTE* pMem2 = NULL;
pMem1 = new BYTE[10]; UINT nTest = (UINT) -1; // pMem zeigt auf 0x01814a28

for (int iTemp = 0; iTemp < 10; iTemp++)
{
    pMem1[iTemp] = (BYTE) iTemp;
}

pMem2 = pMem1 + 5; 		// pMem2 ist danach 0x01814a2d
pMem2 = pMem2 + nTest; 	// pMem2 ist danach 0x01814a2c

In den Kommentaren habe ich die Werte für die Zeiger angegeben, die beim Durchlauf auf meinem System entstanden sind. In Zeile 9 wird pMem2 auf den 6. Index (der Index ist 0-basiert) innerhalb des Arrays gesetzt. In Zeile 10 wird pMem2 um 0xFFFFFFFF verschoben, durch den entstehenden Überlauf ist das mit einer Subtraktion von -1 vergleichbar. pMem2 zeigt nun auf das 5. Element im Array.

Unter x64 lässt sich der Code problemlos kompilieren, auch eine explizite Prüfung auf 64bit-Probleme (Schalter /Wp64) meldet keine Probleme. Spätestens aber beim Versuch, mit pMem2 auf das 5. Element im Array zuzugreifen, funkt Windows mit einer Speicherschutzverletzung dazwischen. Wenn man sich den Inhalt von pMem2 genauer anschaut, dann ist der Grund dafür auch ersichtlich. Anstatt wie erhofft auf 0x00000000:00373d84 zeigt pMem2 plötzlich auf 0x00000001:00373d84, was definitiv nicht zum Speicher des Arrays gehört.

BYTE* pMem1 = NULL; BYTE* pMem2 = NULL;
pMem1 = new BYTE[10]; UINT nTest = (UINT) -1; // pMem zeigt auf 0x00000000:00373d80

for (int iTemp = 0; iTemp < 10; iTemp++)
{
    pMem1[iTemp] = (BYTE) iTemp;
}

pMem2 = pMem1 + 5; 		// pMem2 ist danach 0x00000000:00373d85
pMem2 = pMem2 + nTest; 	// pMem2 ist danach 0x00000001:00373d84

Genau wie unter x32 wird auch unter x64 pMem2 um 0xFFFFFFFF verschoben. Da der Wertebereich der 64bit-Zeiger aber nicht bei 0xFFFFFFFF endet, findet hier kein Überlauf statt und pMem2 zeigt auf eine ungültige Adresse.

Ein kleiner Typecast bei Zeigeroperationen hilft hier weiter. Der einzige Unterschied im folgenden Code zum vorherigen liegt in der letzten Zeile. Wird nTest explizit zu einem vorzeichenbehafteten Typ gecastet, dann zeigt pMem2 nach der Zeigerarithmetik auch auf die gewünschte Stelle.

BYTE* pMem1 = NULL; BYTE* pMem2 = NULL;
pMem1 = new BYTE[10]; UINT nTest = (UINT) -1; // pMem zeigt auf 0x00000000:00373d80

for (int iTemp = 0; iTemp < 10; iTemp++)
{
    pMem1[iTemp] = (BYTE) iTemp;
}

pMem2 = pMem1 + 5; 		// pMem2 ist danach 0x00000000:00373d85
pMem2 = pMem2 + (int) nTest; 	// pMem2 ist danach 0x00000000:00373d84

Alternativ kann man natürlich auch alle vorzeichenlosen Datentypen, die im Zusammenhang mit der Zeigerarithmetik verwendet werden, zu vorzeichenbehafteten umdefinieren. Das ist aber leider auch eine Menge Arbeit und es besteht die Gefahr, dass sich ohne große Not Fehler in bereits getesteten Code einschleichen.

Interessierten Lesern kann ich auch noch ein MSDN-Video von Kang Su Gatlin empfehlen. Hier wird nochmals auf einige Sachen in Bezug auf mögliche Probleme bei der Portierung auf 64bit eingegangen.

GUIDs auf die Schnelle

By Sven on 05.04.2005 - 08:26 in Entwicklung with 1 Kommentar

Ab und zu kommt man mal in die Verlegenheit und braucht schnell eine GUID. Für diese Fälle hat Uwe Keim eine neue Website zur Generierung von GUIDs programmiert.

Wenig bekannt ist aber leider, dass Microsoft mit Windows 2000 eine kleine Änderung bei der Generierung einer GUID durch die Funktion UuidCreate vorgenommen hat. Bis Windows 2000 war eine mit UuidCreate erstellte GUID garantiert immer eindeutig in Raum und Zeit, sofern der Rechner, auf dem sie erstellt wurde, eine Netzwerkkarte besaß. Da man durch die in der GUID enthaltene MAC-Adresse aber Rückschlüsse auf die Netzwerkkennungen in einer Firma schließen kann, hat Microsoft diese Funktion aus Sicherheitsgründen geändert. Der große Nachteil ist nun, dass die generierten GUIDs nicht mehr zwingend eindeutig sind.

Ab Windows 2000 muss man zur Generierung einer globalen eindeutigen GUID daher die Funktion UuidCreateSequential verwenden, andernfalls kann lediglich garantiert werden, dass die GUID nur auf dem Rechner eindeutig ist, auf dem sie erstellt wurde. Gerade für COM-Komponenten ist es aber wichtig, dass die verwendeten GUIDs auch wirklich einzigartig sind. Im schlechtesten Fall kann es sonst passieren, dass zwei verschiedene Komponenten von verschiedenen Entwicklern zufällig eine gleiche GUID benutzen und sich so ziemlich in die Quere kommen.

Dass die bei Uwe generierten GUIDs nicht global eindeutig sind, erkennt man am letzten Block. Dieser stellt die MAC-Adresse dar, global eindeutige GUIDs von einem Rechner haben hier immer den gleichen Wert.

Bei Uwe erzeugte GUIDs:

  • 1ab9096f-2101-4d6a-9a77-4b0be04d6b0a
  • 2e27197f-4c53-45f1-8f83-0f9f89456962
  • cc09efd4-967c-4eb1-8c4b-3023d9c85c1c
  • e0297aad-cee3-4005-a4db-172af5fa8295
  • ea0bc131-97fd-4c15-8007-2d79995c39e2

Mit uuidgen.exe erzeugte global eindeutige GUIDs (uuidgen.exe -x -n5):

  • f225f880-a5a2-11d9-8512-000c6e33eb97
  • f225f881-a5a2-11d9-8512-000c6e33eb97
  • f225f882-a5a2-11d9-8512-000c6e33eb97
  • f225f883-a5a2-11d9-8512-000c6e33eb97
  • f225f884-a5a2-11d9-8512-000c6e33eb97

So wie ich Uwe kenne, wird er seine Seite aber ziemlich bald aktualisieren, so dass man bei Bedarf auch global eindeutige GUIDs erhält. 🙂

Wurzelproblem im Netzwerk

By Sven on 28.01.2005 - 16:40 in Entwicklung with Keine Kommentare

Ein Betatester machte mich gestern auf ein fehlerhaftes Verhalten von SpeedCommander in Zusammenhang mit gesperrten Tabs und UNC-Namen aufmerksam. Wenn man sich ein Tab mit einem UNC-Namen als Basisordner definiert und den Laufwerkswechsel sperrt, dann aktiviert SpeedCommander unter Umständen auch ein freies Tab, wenn man in einen Ordner innerhalb der gleichen Freigabe wechselt.

Im Debugger war zu erkennen, dass die Funktion PathIsSameRoot anscheinend falsche Ergebnisse liefert. SpeedCommander prüft mit dieser Funktion, ob sich zwei Ordner auf dem gleichen Laufwerk befinden:

// Laufwerkswechsel ist gesperrt
if (pTabLayoutFolder->m_dwFlags & FOLDERTAB_LOCKDRIVES)
{
    // Kein Basisordner oder ungleiches Laufwerk
    if (!Lwl_PathIsSame(pTabLayoutFolder->m_strBaseFolder, strNewFolder) && 
        !PathIsSameRoot(strCurrentFolder, strNewFolder))
    {
        // Wechsel zum freien Tab
        if (-1 != iFirstUnlockedTab || fCreateNewTab)
            goto DisplayFolder;

        // Fehlermeldung und weg
        ShowInfoTip(IDS_TAB_INFOTIP_LOCKDRIVES);
        return FALSE;
    }

	...
}

Wenn sich die beiden Ordner auf unterschiedlichen Laufwerken befinden, dann wird bei gesperrtem Laufwerkswechsel entweder auf das erste nicht gesperrte Tab gewechselt oder ein InfoTip angezeigt und der Wechsel verhindert.

PathIsSameRoot bekommt die zu testenden Ordner als Parameter übergeben und gibt TRUE zurück, wenn sich beide Ordner auf das gleiche Laufwerk beziehen. Bei den Ordnern C:\Windows und C:\Programme wird TRUE zurückgegeben, während bei C:\Windows und D:\Programme FALSE zurückgegeben wird.

Bei UNC-Pfaden ist die Freigabeebene die Root, also z.B. \\Server\Daten. PathIsSameRoot prüft bei UNC-Pfaden also, ob sich beide übergebenen Ordner auf die gleiche Freigabe beziehen:

\\Server1\Daten\Test und \\Server1\Daten\Test2
—> Gleiche Root

\\Server1\Daten\Test und \\Server1\Ablage\Test
—> Ungleiche Root

\\Server1\Daten\Test und \\Server2\Daten\Test2
—> Ungleiche Root

\\Server\Daten\Test und \\Server\Daten
–> Gleiche Root

\\Server\Daten und \\Server\Daten\Test
—> Ungleiche Root (Falsch!)

Das letzte Beispiel zeigt, dass die Funktion PathIsSameRoot offensichtlich Probleme hat, wenn der erste Parameter einen Freigabeordner (also eine Root) darstellt. Die beiden Parameter sind die gleichen wie im Beispiel davor, nur die Reihenfolge ist geändert. Doch warum gibt die Funktion hier unterschiedliche Ergebnisse zurück?

Schauen wir uns einfach einmal die WINE-Implementation der Funktion an, die etwas anschaulicher ist als der Assembler-Quelltext aus dem Debugger:

BOOL WINAPI PathIsSameRootA(LPCSTR lpszPath1, LPCSTR lpszPath2)
{
    LPCSTR lpszStart;
    int dwLen;

    TRACE("(%s,%s)\n", debugstr_a(lpszPath1), debugstr_a(lpszPath2));

    if (!lpszPath1 || !lpszPath2 || !(lpszStart = PathSkipRootA(lpszPath1)))
        return FALSE;

    dwLen = PathCommonPrefixA(lpszPath1, lpszPath2, NULL) + 1;
    if (lpszStart - lpszPath1 > dwLen)
        return FALSE; /* Paths not common up to length of the root */

    return TRUE;
}

Die Funktion prüft, ob lpszPath1 und lpszPath2 gültige Zeiger sind. PathSkipRoot liefert einen Zeiger auf das erste Zeichen, das der Root folgt oder NULL, wenn keine Root ermittelt werden konnte. Mit PathCommonPrefix wird die Anzahl der gleichen Zeichen ermittelt, mit denen beide Ordnernamen anfangen. Anschließend wird geprüft, ob die Anzahl der gemeinsamen Zeichen mindestens so groß ist wie die Länge der ermittelten Root von lpszPath1. Wenn ja, dann zeigen beide Pfade auf die gleiche Root.

Die Schwachstelle scheint hier PathSkipRoot zu sein. Wenn man die Funktion einmal jeweils direkt mit C:\ und \\Server\Freigabe aufruft, dann gibt PathSkipRoot bei C:\ einen Zeiger auf das abschließende NULL-Zeichen zurück, bei \\Server\Freigabe jedoch einen NULL-Zeiger. Das veranlasst PathIsSameRoot, die Abarbeitung zu beenden und FALSE zurückzugeben.

Doch selbst wenn Microsoft diesen Fehler im nächsten Service Pack fixen sollte, so wird es immer noch eine große Anzahl von Windows-Systemen geben, bei denen eine SHLWAPI.DLL mit einer nicht korrekten Implementierung von PathIsSameRoot Verwendung findet. Die einzige Möglichkeit, den Fehler in der eigenen Anwendung dauerhaft zu beheben, ist daher die Umgehung von PathIsSameRoot und die Implementation einer eigenen Funktion:

BOOL Lwl_PathIsSameRoot(LPCTSTR pszPath1, LPCTSTR pszPath2)
{
    // Pruefen auf Gueltigkeit
    if (NULL == pszPath1 || NULL == pszPath2)
        return FALSE;

    // Temporaere Kopien erstellen
    TCHAR szPath1[MAX_PATH+1], szPath2[MAX_PATH+1]; 
    StringCchCopy(szPath1, countof(szPath1), pszPath1);
    StringCchCopy(szPath2, countof(szPath2), pszPath2);

    // Beide Pfade bis auf die Root kuerzen
    if (!PathStripToRoot(szPath1) || !PathStripToRoot(szPath2))
        return FALSE;

    // Root von beiden Pfaden vergleichen
    return (0 == lstrcmpi(szPath1, szPath2)) ? TRUE : FALSE;
}

Das gleiche gilt leider auch für PathSkipRoot und alle Funktionen, die PathSkipRoot intern verwenden. Hier steht man nun vor der Entscheidung, generell auf die doch sehr komfortablen Pfadfunktionen zu verzichten oder darauf zu hoffen, dass es keine negativen Auswirkungen von PathSkipRoot auf die anderen Funktionen gibt.

Imageliste mit System

By Sven on 10.01.2005 - 10:00 in Entwicklung with 2 Kommentare

Eine Imageliste ist eine Ansammlung von Bildobjekten gleicher Größe und Farbauflösung, die anhand ihres Index innerhalb der Liste referenziert werden. Die einzelnen Bildobjekte können auf einfache und vielfältige Weise gezeichnet werden. Listenansicht und Baumansicht nutzen die Imageliste zur Darstellung von Symbolen, jeder Eintrag in den beiden Ansichten kann als Eigenschaft einen Index des darzustellenden Symbols innerhalb einer Imageliste enthalten. Die zu verwendende Imageliste wird den beiden Ansichten mit einer Funktion zugewiesen.

Die Systemimageliste ist eine von der Shell erzeugte und gepflegte Imageliste, genaugenommen sind es sogar mehrere. Bis einschließlich Windows 2000 verwendet die Shell zwei Imagelisten (16×16 und 32×32), mit Windows XP kam eine dritte (48×48) hinzu. Unter Windows 9x/ME wird für alle Prozesse eine gemeinsame Systemimageliste verwendet, jede Manipulation hat auch Auswirkungen auf alle anderen Prozesse. Unter Windows NT/2000/XP erhält jeder Prozess eine eigene Systemimageliste, Änderungen an dieser betreffen dann nur noch die eigene und nicht die der anderen Prozesse.

Der Zugriff auf die Systemimageliste erfolgt über die Funktion SHGetFileInfo. Der Rückgabewert von SHGetFileInfo ist ein Handle auf eine Imageliste, entweder auf die kleine (16×16) oder auf die große (32×32). Die extragroße Imageliste (48×48) unter Windows XP kann durch SHGetFileInfo nicht ermittelt werden, hierfür muss die Funktion SHGetImageList verwendet werden. Zur einfachen Handhabung habe ich mir eine Funktion Lwl_ShlGetSystemImageList geschrieben, die alle nötigen Aufrufe kapselt und abwärtskompatibel bis Windows 95 ist. Die Parameter sind SHIL_LARGE (32×32), SHIL_SMALL (16×16) und SHIL_EXTRALARGE (48×48), alle 3 Konstanten sind in der Headerdatei ShellAPI.h definiert.

HIMAGELIST Lwl_ShlGetSystemImageList(int iImageList)
{
    // 16x16 und 32x32 auf konventionelle Art
    if (SHIL_SMALL == iImageList || SHIL_LARGE == iImageList)
    {
        // Flags zusammenstellen
        SHFILEINFO sfi; DWORD dwFlags = SHGFI_SYSICONINDEX | SHGFI_USEFILEATTRIBUTES;
        dwFlags |= (SHIL_SMALL == iImageList ? SHGFI_SMALLICON : SHGFI_LARGEICON);

        // Imageliste abrufen
        return (HIMAGELIST) SHGetFileInfo(_T("123.txt"), 0, &sfi, sizeof(SHFILEINFO), dwFlags);
    }

    // 48x48-Imageliste abrufen
    HIMAGELIST hImageList = NULL;
    if (S_OK != _SHGetImageList(iImageList, IID_IImageList, (void**) &hImageList))
        return NULL;

    // Ans kloar
    return hImageList;
}

Die Funktion _SHGetImageList importiert SHGetImageList dynamisch und ruft diese bei Verfügbarkeit auf. Somit ist die Lauffähigkeit auch unter älteren Windows-Versionen sichergestellt.

typedef HRESULT (STDMETHODCALLTYPE *SHGETIMAGELIST)(int, REFIID, void**);

STDAPI _SHGetImageList(int iImageList, REFIID riid, void** ppv)
{
    // Alles wird gut
    HRESULT hResult = S_OK;

    // Dll laden
    HINSTANCE hInstShell32 = LoadLibrary(_T("Shell32.dll"));
    if (NULL == hInstShell32)
        return HRESULT_FROM_WIN32(GetLastError());

    // Funktion anhand des Namens importieren
    SHGETIMAGELIST pfn = (SHGETIMAGELIST) GetProcAddress(hInstShell32, "SHGetImageList");

    // Funktion aufrufen
    if (NULL != pfn)
    {
        hResult = (pfn)(iImageList, riid, ppv);
    }

    // Funktion ist nicht vorhanden
    else
    {
        hResult = HRESULT_FROM_WIN32(GetLastError());
    }

    // Dll freigeben
    FreeLibrary(hInstShell32);

    // Ans kloar
    return hResult;
}

Lwl_ShlGetSystemImageList gibt das Handle auf die gewünschte Imageliste zurück. Wenn diese noch nicht existiert, dann werden unter Windows NT/2000/XP alle zwei (bzw. ab XP drei) Imagelisten erstellt und mit folgenden Symbolen gefüllt:

  • Freigabesymbol (Overlay)
  • Verknüpfungssymbol (Overlay)
  • Offlinesymbol (Overlay)
  • Unbekannter Dateityp
  • Textdatei

Unter Windows 9x/ME ist mit sehr großer Wahrscheinlichkeit der Explorer die erste Anwendung, welche die Systemimageliste initialisiert. Alle anderen nachfolgenden Prozesse bekommen dann einen Verweis auf die bereits gut gefüllte Imageliste zurück.

Jeder nachfolgende Aufruf von SHGetFileInfo zur Ermittlung von Dateisymbolen fügt das entsprechende Symbol dann der Systemimageliste hinzu, sofern es noch nicht in dieser enthalten ist. Alle zwei (oder drei) Imagelisten werden synchron gefüllt, so dass ein Index für ein Symbol in der 16×16-Liste gleich dem Index in der 32×32-Liste ist.

Langsame Laufwerke

By Sven on 06.01.2005 - 22:45 in Entwicklung

Bei der Optimierung der Dateianzeige für VPN-Laufwerke bin ich über die Funktion PathIsSlow gestolpert. Die Dokumentation im Platform SDK ist nicht gerade aussagekräftig, eine Eigenschaft, die leider für viele Funktionen zutrifft, welche erst 2002 im Zuge des Antitrust-Prozesses gegen Microsoft dokumentiert wurden. In der Funktionsbeschreibung zu PathIsSlow steht

Determines whether a file path is a high-latency network connection.

Doch nach welchen Kriterien legt Windows fest, ob es sich für den angegebenen Dateinamen um eine langsame oder um eine schnelle Verbindung handelt?

Um dies herauszufinden, habe ich mir die Funktion im Debugger mal etwas näher angeschaut. Mit Hilfe der von Microsoft angebotenen Symboldateien lässt sich mit ein wenig Assembler-Kenntnis einiges herausfinden. PathIsSlow ist eine recht kurze Funktion und sieht wie folgt aus:

PathIsSlowW@8:
773DA350 push esi
773DA351 mov esi,dword ptr [esp+8]
773DA355 push esi
773DA356 call dword ptr [__imp__PathIsUNCW@4 (773a20a8)]
773DA35C test eax,eax
773DA35E push esi
773DA35F jne _PathIsSlowW@8+11h (774478bc)
773DA365 call dword ptr [__imp__PathGetDriveNumberW@4 (773a20e4)]
773DA36B push eax
773DA36C call _CMtPt_IsSlow@4 (773da388)
773DA371 test eax,eax
773DA373 jne _PathIsSlowW@8+21h (773da383)
773DA375 push dword ptr [esp+0Ch]
773DA379 push esi
773DA37A call _PathIsHighLatency@8 (773da3b8)
773DA37F pop esi
773DA380 ret 8
773DA383 xor eax,eax
773DA385 inc eax
773DA386 jmp _PathIsSlowW@8+40h (773da37f)

Zuerst wird mit Hilfe der Funktion PathIsUNC geprüft, ob der Dateiname in der UNC-Konvention (\\Server\Freigabe) vorliegt. Wenn ja, verzweigt PathIsSlow zur Adresse 0x774478bc. Befindet sich der Dateiname dagegen auf einem gemappten Laufwerk, dann wird über PathGetDriveNumber der Laufwerksbuchstabe ermittelt und mit diesem die Funktion CMtPt_IsSlow aufgerufen:

_CMtPt_IsSlow@4:
773DA388 push esi
773DA389 push edi
773DA38A push 1
773DA38C xor edi,edi
773DA38E push edi
773DA38F push dword ptr [esp+14h]
773DA393 call CMountPoint::GetMountPoint (773cb43b)
773DA398 mov esi,eax
773DA39A cmp esi,edi
773DA39C je _CMtPt_IsSlow@4+29h (773da3b1)
773DA39E mov eax,dword ptr [esi]
773DA3A0 mov ecx,esi
773DA3A2 call dword ptr [eax+0CCh]
773DA3A8 mov ecx,esi
773DA3AA mov edi,eax
773DA3AC call CMountPoint::Release (773cb385)
773DA3B1 mov eax,edi
773DA3B3 pop edi
773DA3B4 pop esi
773DA3B5 ret 4

In dieser Funktion wird geprüft, ob es für den gewünschten Laufwerksbuchstaben ein CMountPoint-Objekt gibt. Im Erfolgsfall wird CMtPtRemote::IsSlow (0x773DA3A2) aufgerufen:

_IsSlow@CMtPtRemote@@EAEHXZ:
77521018 push esi
77521019 xor esi,esi
7752101B call CMtPtRemote::_GetPathSpeed (77517e98)
77521020 test eax,eax
77521022 je CMtPtRemote::_IsSlow+14h (7752102c)
77521024 cmp eax,190h
77521029 ja CMtPtRemote::_IsSlow+14h (7752102c)
7752102B inc esi
7752102C mov eax,esi
7752102E pop esi
7752102F ret

Windows verzweigt weiter zur Funktion CMtPtRemote::GetPathSpeed:

_GetPathSpeed@CMtPtRemote@@AAEKXZ:
77517E98 push esi
77517E99 lea esi,[ecx+25Ch]
77517E9F cmp dword ptr [esi],0
77517EA2 jne CMtPtRemote::_GetPathSpeed+11h (77517ea9)
77517EA4 call CMtPtRemote::_CalcPathSpeed (77517cbb)
77517EA9 mov eax,dword ptr [esi]
77517EAB pop esi
77517EAC ret

und prüft, ob die Geschwindigkeit für das MountPoint-Objekt bereits berechnet und gespeichert wurde. Wenn ja, dann wird dieser Wert zurückgegeben. Ansonsten verzweigt Windows zur Funktion CMtPtRemote::CalcPathSpeed und berechnet mit Hilfe von MultinetGetConnectionPerformance die Geschwindigkeit. Diese wird in einer NETCONNECTINFOSTRUCT-Struktur zurückgeben und besitzt folgende Bedeutung:

Speed of the media to the network resource, in 100 bits-per-second (bps).

For example, a 1200 baud point-to-point link returns 12. A value of zero indicates that no information is available. A value of one indicates that the actual value is greater than the maximum that can be represented by the member.

In der Funktion CMtPtRemote::IsSlow sieht man an der Adresse 0x77521024, dass die ermittelte Geschwindigkeit mit 0x190 (400) verglichen wird. Ist sie größer als 400, dann gilt das Laufwerk als schnell; ist sie kleiner oder gleich 400, dann ist das Laufwerk langsam. 40000 bps ist also die Grenze für ein langsames Laufwerk. Nach meinem Verständnis ist dies eine normale Modemverbindung, anhand der gemessenen Werten ist für mich die Bedeutung dieses Wertes allerdings nicht ganz klar:

Verbindung Geschwindigkeit
1 MBit (VPN DSL)    104
10 MBit 948
54 MBit (WLAN) 2044
100 MBit 2115
1 GBit 7972

Die VPN-Verbindung fällt noch eindeutig in den langsamen Bereich, während die 10 MBit-Netzwerkanbindung schon deutlich zu schnell ist, um noch als langsam zu gelten.

Doch nun wieder zurück zu PathIsSlow. Die Ermittlung der Geschwindigkeit für UNC-Namen in der Funktion GetPathSpeed erfolgt ebenfalls über MultinetGetConnectionPerformance, den Abdruck der Funktion habe ich mir jetzt erspart. Sollte die Geschwindigkeitsprüfung für den Dateinamen ergeben, dass er sich nicht auf einem langsamen Laufwerk befindet, ruft Windows noch die Funktion PathIsHighLatency auf. Diese ist leider nicht dokumentiert und sieht wie folgt aus:

_PathIsHighLatency@8:
773DA3B8 mov eax,dword ptr [esp+8]
773DA3BC push esi
773DA3BD xor esi,esi
773DA3BF cmp eax,0FFFFFFFFh
773DA3C2 jne _PathIsHighLatency@8+26h (773da3d9)
773DA3C4 cmp dword ptr [esp+8],esi
773DA3C8 je _PathIsHighLatency@8+1Eh (773da3e8)
773DA3CA push dword ptr [esp+8]
773DA3CE call dword ptr [__imp__GetFileAttributesW@4 (773a1a38)]
773DA3D4 cmp eax,0FFFFFFFFh
773DA3D7 je _PathIsHighLatency@8+2Eh (773da3e2)
773DA3D9 test ah,10h
773DA3DC jne _PathIsHighLatency@8+2Bh (774478b4)
773DA3E2 mov eax,esi
773DA3E4 pop esi
773DA3E5 ret 8
773DA3E8 or eax,0FFFFFFFFh
773DA3EB jmp _PathIsHighLatency@8+21h (773da3d4)

Anhand der Dateiattribute, die der Funktion PathIsSlow übergeben worden sind, wird geprüft, ob das Attribut FILE_ATTRIBUTE_OFFLINE gesetzt ist. Dieses Flag wird von Remote Storage Systemen verwendet und gibt an, dass die Daten im Moment ausgelagert sind. Werden an PathIsSlow keine Dateiattribute übergeben, so ermittelt PathIsHighLatency die Attribute über GetFileAttributes selbst.

Das Rätsel um langsame Laufwerke ist nun (fast) gelüftet. PathIsSlow nutzt die Funktion MultinetGetConnectionPerformance, um die Geschwindigkeit zu bestimmen. Zusätzlich wird noch geprüft, ob sich die Datei auf einem Remote Storage System befindet und dort im Moment ausgelagert ist. Unklar ist bisher leider noch, wie sich die von MultinetGetConnectionPerformance zurückgegebene Geschwindigkeit einordnen lässt.

Top