FAQ der Newsgroup de.comp.lang.assembler (d.c.l.a.)

Wie komme ich an die Details im Windows-Explorer?

Der Windows Explorer (nicht der Internet Explorer) ist der Standard-Dateimanager in Windows. Hiermit kann man sich die hierarchische Struktur der Dateien, Ordner und Laufwerke auf dem Computer zeigen lassen sowie erstellen, kopieren, verschieben und umbenennen. Über Erweiterungen sind weitere und neue Operationen möglich.

In der Details-Ansicht werden auf der rechten Seite Dateien und Ordner mit der Angabe einiger Details - wie Name, Größe, Typ, Datum - gezeigt:

Screenshot des Explorer-Fensters

Welche Details angezeigt werden, kann man einstellen, indem man über die Zeile mit den Spaltenüberschriften zieht, auf die rechte Maustaste klickt und mit der linken Maustaste das gewünschte Detail anklickt:

Screenshot des Explorer-Fensters

Es gibt eine Menge Details, an die man gerne auch mit einem Programm kommen möchte. Die zuständige Shell-Methode für diese Details heißt ab Win2000 IShellFolder2::GetDetailsOf. Für ältere Windows-Versionen hat Microsoft die zunächst undokumentierte Methode IShellDetails::GetDetailsOf nun dokumentiert. Hier wird sie nicht weiter vertieft.

Als Shell-Objekt bzw. OLE-Objekt ist es nicht ganz einfach sich diese Methode dienstbar zu machen. Der Gang ist folgendermaßen:

  1. CoInitialize,ShGetMalloc
  2. GetShDeskTopFolder
  3. DeskTop-Folder -> ParseDisplayName
  4. DeskTop-Folder -> BindToObject
  5. MeinFolder -> ParseDisplayName
  6. pMeinFolder -> QueryInterface
  7. pShellFolder2->GetDetailsOf, StrRetToStr
Nasm & GoLink
; Assemblieren: nasm.exe -fwin32 -o <Name>.obj <NameExt>.nasm
; Linken:       GoLink.exe /console /entry _main <Name>.obj
;                          kernel32.dll shell32.dll shlwapi.dll ole32.dll user32.dll
; Aufrufen:      <Name>.exe <VollerPfad>
; Achtung:      1) Keinerlei Fehlerprüfung
;               2) Die gewünschte Datei muss mit vollem Pfad angegeben werden,
;                  z.B.: c:\tmp\test.jpg

BITS 32
EXTERN GetCommandLineW, LocalFree, \
       AttachConsole, GetStdHandle, WriteFile               ; kernel32.dll
EXTERN CommandLineToArgvW, SHGetMalloc, SHGetDesktopFolder  ; shell32.dll
EXTERN StrRetToStrA                                         ; shlwapi.dll
EXTERN CoInitialize,CoUninitialize,CoTaskMemFree            ; ole32.dll
EXTERN CharToOemA, wsprintfA                                ; user32.dll

SECTION .data
    IID_IShellFolder:   ; 000214E6-0000-0000-C000-000000000046
                        dd 0x000214E6
                        dw 0,0
                        db 0xC0,0,0,0,0,0,0,0x46
    IID_IShellFolder2:  ; 93F2F68C-1D1B-11d3-A30E-00C04F79ABD1
                        dd 0x93F2F68C
                        dw 0x1D1B,0x11d3
                        db 0xA3,0x0E,0,0xC0,0x4F,0x79,0xAB,0xD1
    wsprintfFormat      db '%2d %s: %s',10,0

SECTION .bss
    pMalloc             RESD 1
    pDesktopFolder      RESD 1
    pMeinFolder         RESD 1
    pShellFolder2       RESD 1
    pidlPfad            RESD 1
    pidlDatei           RESD 1
    pTitel              RESD 1
    pDetail             RESD 1
    pDriveDir           RESD 1
    pNameExt            RESD 1
    szArglist           RESD 1          ; CommandLineToArgvW
    nArgs               RESD 1          ; CommandLineToArgvW
    len                 RESD 1          ; Länge von echo (Resultat wsprintfA)
    dummy               RESD 1
    sd                  RESB 272        ; SHELLDETAILS
    echo                RESD 260

SECTION .text
GLOBAL _main
_main:

    ; 1. Kommandozeilenargument splitten: drive:\dir\name.ext -> drive:\dir<null>name.ext
    call GetCommandLineW
    push nArgs                  ; int *pNumArgs
    push eax                    ; LPCWSTR lpCmdLine von GetCommandLineW
    call CommandLineToArgvW
    mov [szArglist], eax
    mov esi, [eax+4]            ; 1. Parameter
    mov [pDriveDir], esi
.A: ; Dateinamen-Backslash suchen und nullen
    lodsw                       ; UTF16-Buchstabe (2 Bytes)
    cmp ax, '\'                 ; Backslash?
    cmovz edx, esi              ; if (z) {edx = esi}
    test ax, ax                 ; Ende der Kommandozeile?
    jne .A                      ; nein: Schleife
    mov word [edx-2], 0         ; letzten Backslash nullen
    mov [pNameExt], edx         ; Zeiger auf Dateinamen

    ; CoInitialize (NULL)
    push 0
    call CoInitialize

    ; Memory Manager zur Befreiung der PIDLs
    push pMalloc                ; LPMALLOC *ppMalloc
    call SHGetMalloc

    ; IShellFolder
    push pDesktopFolder         ; LPSHELLFOLDER *ppshf
    call SHGetDesktopFolder

    ; Dir -> ITEMIDLIST (pDesktopFolder->ParseDisplayName)
    push 0                      ; ULONG *pdwAttributes
    push pidlPfad               ; PIDLIST_RELATIVE *ppidl
    push 0                      ; ULONG *pchEaten
    push dword [pDriveDir]      ; LPWSTR pszDisplayName
    push 0                      ; IBindCtx *pbc
    push 0                      ; HWND hwnd
    mov edx, [pDesktopFolder]   ; this
    push edx
    mov eax, [edx]
    call [eax+12]               ; IShellFolder::ParseDisplayName

    ; pMeinFolder = pDesktopFolder->BindToObject
    push pMeinFolder            ; VOID** ppvResult
    push IID_IShellFolder       ; GUID*  riid,
    push 0                      ; IBindCtx* pbc,
    push dword [pidlPfad]       ; LPCITEMIDLIST pidl
    mov edx, [pDesktopFolder]   ; this
    push edx
    mov eax, [edx]
    call [eax+20]               ; IShellFolder::BindToObject

    ;pMalloc->Free(pidlPfad);
    push dword [pidlPfad]       ; LPCITEMIDLIST pidl
    mov edx, [pMalloc]          ; this
    push edx
    mov eax, [edx]
    call [eax+20]               ; IMalloc::Free

    ;pDesktopFolder->Release();
    mov edx, [pDesktopFolder]   ; this
    push edx
    mov eax, [edx]
    call [eax+8]                ; IShellFolder::Release

    ; Datei -> ITEMIDLIST (pMeinFolder->ParseDisplayName)
    push 0                      ; ULONG *pdwAttributes
    push pidlDatei              ; PIDLIST_RELATIVE *ppidl
    push 0                      ; ULONG *pchEaten
    push dword [pNameExt]       ; LPWSTR pszDisplayName
    push 0                      ; IBindCtx *pbc
    push 0                      ; HWND hwnd
    mov edx, [pMeinFolder]      ; this
    push edx
    mov eax, [edx]
    call [eax+12]               ; IShellFolder::ParseDisplayName

    ; pShellFolder2 = pMeinFolder->QueryInterface
    push pShellFolder2          ; void **ppvObject
    push IID_IShellFolder2      ; REFIID riid
    mov edx, [pMeinFolder]      ; this
    push edx
    mov eax, [edx]
    call [eax]                  ; IShellFolder::QueryInterface

    ; pShellFolder2->AddRef()
    mov edx, [pShellFolder2]    ; this
    push edx
    mov eax, [edx]
    call [eax+4]                ; IShellFolder2::AddRef

    ; for (esi=0;;++esi)        esi ist die laufende Nummer der Spalte
    xor esi, esi
    not esi
.FOR:
    add esi, 1

    ; pShellFolder2->GetDetailsOf(NULL, i, &sd)
    push sd                     ; SHELLDETAILS *psd
    push esi                    ; UINT iColumn
    push 0                      ; PCUITEMID_CHILD pidl = 0 (Spaltentitel ermitteln)
    mov edx, [pShellFolder2]    ; this
    push edx
    mov eax, [edx]
    call [eax+72]               ; IShellFolder2::GetDetailsOf
    test eax, eax
    jne .ENDFOR                 ; keine Spaltentitel mehr: Break

    ; StrRetToStr (&sd.str,NULL,&pTitel)
    push pTitel                 ; LPTSTR *ppszName
    push 0                      ; PCUITEMID_CHILD pidl
    push (sd + 8)               ; STRRET *pstr (SHELLDETAILS.str)
    call StrRetToStrA

    ; pShellFolder2->GetDetailsOf(pidlDatei, i, &sd)
    push sd                     ; SHELLDETAILS *psd
    push esi                    ; UINT iColumn
    push dword [pidlDatei]      ; PCUITEMID_CHILD pidl
    mov edx, [pShellFolder2]    ; this
    push edx
    mov eax, [edx]
    call [eax+72]               ; IShellFolder2::GetDetailsOf
    test eax, eax               ; Detail gefunden?
    jnz short .FOR              ; Nein: Continue

    ; StrRetToStr (&sd.str,NULL,&pDetail)
    push pDetail                ; LPTSTR *ppszName
    push 0                      ; PCUITEMID_CHILD pidl
    push (sd + 8)               ; STRRET *pstr (SHELLDETAILS.str)
    call StrRetToStrA

    ; sprintf (echo,"%2d %s: %s\n",i,pTitel,pDetail)
    push dword [pDetail]
    push dword [pTitel]
    push esi
    push wsprintfFormat         ; LPCTSTR lpFmt
    push echo                   ; LPTSTR lpOut
    call wsprintfA
    add esp, 20                 ; cdecl
    mov [len], eax              ; Länge speichern für WriteFile

    ; CoTaskMemFree (pTitel); CoTaskMemFree (pDetail)
    push dword [pTitel]         ; LPVOID pv
    call CoTaskMemFree
    push dword [pDetail]        ; LPVOID pv
    call CoTaskMemFree

    ; CharToOem (Ansi,Oem) - auskommentieren, wenn ANSI-Ausgabe gewünscht
    push echo                   ; LPSTR lpszDst
    push echo                   ; LPCTSTR lpszSrc,
    call CharToOemA

    ; Ausgabe auf StdOut
    push -11                    ; DWORD nStdHandle = STD_OUTPUT_HANDLE
    call GetStdHandle
    push 0                      ; LPOVERLAPPED lpOverlapped
    push dummy                  ; LPDWORD lpNumberOfBytesWritten
    push dword [len]            ; DWORD nNumberOfBytesToWrite
    push echo                   ; LPCVOID lpBuffer
    push eax                    ; HANDLE hFile: StdOut von GetStdHandle
    call WriteFile

    jmp near .FOR
.ENDFOR:

    ;pMalloc->Free(pidlDatei);
    push dword [pidlDatei]      ; LPCITEMIDLIST pidl
    mov edx, [pMalloc]          ; this
    push edx
    mov eax, [edx]
    call [eax+20]               ; IMalloc::Free

    ;pMeinFolder->Release();
    mov edx, [pMeinFolder]      ; this
    push edx
    mov eax, [edx]
    call [eax+8]                ; IShellFolder::Release

    ; pMalloc -> Release
    mov edx, [pMalloc]          ; this
    push edx
    mov eax, [edx]
    call [eax+8]                ; IMalloc::Release

    ; CoUninitialize ()
    call CoUninitialize

    ; Kommondozeilenargumente befreien
    push dword [szArglist]      ;  HLOCAL hMem
    call LocalFree

    ; Ende
    mov eax, 0                  ; Exitcode
    ret                         ; Ende

Weitere Informationen:

Using COM in Assembly Language
http://www.codebreakers-journal.com/content/view/166/96/

Ralph 'rkhb' Bauer März 2009