readme.md (4544B)
1 # icns shell extension 2 3 A Windows shell extension that renders thumbnails for `.icns` files in 4 Explorer. It is an in-process COM server implementing `IInitializeWithStream` 5 and `IThumbnailProvider`, written in Go. 6 7 ## How it works 8 9 - `internal/com` has the minimal COM plumbing: GUIDs, `HRESULT`s, the 10 `IUnknown`/`IClassFactory` vtables, an `IStream` → `io.Reader` adapter, and a 11 generic class factory. 12 - `internal/provider` is the COM object. Each instance is a Go struct whose 13 first two fields are vtable pointers (one per interface); the vtables are 14 package-level singletons filled with `syscall.NewCallback` trampolines. 15 Instances are reference counted, pinned, and tracked in a global set so the 16 GC never frees an object the shell still points at. 17 - `main.go` exports the entry points COM requires (`DllGetClassObject`, 18 `DllCanUnloadNow`, `DllRegisterServer`, `DllUnregisterServer`, `DllInstall`). 19 This is the only place cgo appears, and it contains no C code; cgo is needed 20 purely because `go build -buildmode=c-shared` requires it. 21 - `register.go` writes the registry entries and notifies the shell. 22 23 Thumbnail selection picks the smallest icon that is at least the requested size 24 and downsamples it with Lanczos; if none is large enough the largest icon is 25 returned as-is. The bitmap handed to the shell is a top-down 32bpp DIB with 26 premultiplied alpha (`WTSAT_ARGB`). 27 28 ## Building 29 30 A GCC-compatible toolchain (mingw-w64) must be on `PATH`; cgo does not support 31 MSVC. With scoop: `scoop install mingw`. 32 33 ```powershell 34 go build -buildmode=c-shared -o icns-shellext.dll . 35 ``` 36 37 ## Installing 38 39 Keep the DLL where it is registered from; the absolute path is written to the 40 registry. There are two modes, chosen by whether `regsvr32` runs elevated: 41 42 | | Per-user (`regsvr32` from a normal prompt) | Machine-wide (`regsvr32` from an elevated prompt) | 43 |---|---|---| 44 | Registry | `HKCU\Software\Classes` | `HKLM\SOFTWARE\Classes` + Shell Extensions `Approved` list | 45 | Hosting | Inside `explorer.exe` (`DisableProcessIsolation=1`) | Isolated `dllhost.exe` surrogate (the shell default) | 46 | Trade-off | No elevation; a crash in the provider would take Explorer down | Safer hosting; needs admin once | 47 48 ```powershell 49 regsvr32 icns-shellext.dll # register 50 regsvr32 /u icns-shellext.dll # unregister (run elevated to remove an HKLM registration) 51 ``` 52 53 Why the per-user mode runs in-process: the shell's thumbnail surrogate only 54 resolves handler CLSIDs from HKLM. A CLSID registered solely under HKCU fails 55 there with `REGDB_E_CLASSNOTREG` (observed on Windows 11 23H2), and any HKLM 56 entry for the CLSID, including a stale one pointing at a DLL that no longer 57 exists, shadows the per-user entry. Elevated registration removes the per-user 58 entries so the machine-wide, isolated configuration is the one in effect. 59 60 Explorer caches thumbnails, so files viewed before installation may keep their 61 generic icon until the cache is rebuilt (Disk Cleanup → Thumbnails, or copy the 62 file). In the isolated mode a replaced DLL only takes effect once the surrogate 63 exits (a few seconds idle) or you run `taskkill /f /im dllhost.exe`; in the 64 per-user mode Explorer itself has to be restarted. 65 66 ## Testing 67 68 `go test .` builds the DLL, loads it, and drives it through the raw COM 69 vtables: class factory, `Initialize` with an in-memory `IStream`, interface 70 identity, `GetThumbnail` at several sizes, and pixel-level checks of the 71 returned bitmap (channel order, premultiplied alpha, top-down rows). 72 73 `ICNS_SHELLEXT_E2E=1 go test -run TestExplorer .` additionally asks the shell 74 itself (`IShellItemImageFactory`) for a thumbnail of a temporary `.icns` file, 75 which exercises the registry entries and whichever hosting mode is registered. 76 It requires the DLL to be registered first. 77 78 ## Debugging 79 80 Registration failures are logged to the file named by the `ICNS_SHELLEXT_LOG` 81 environment variable when set. Thumbnail extraction errors go to the host 82 process's stderr, which is normally discarded; the in-process test above is the 83 easiest way to reproduce a decoding problem. 84 85 Useful `HRESULT`s from `IShellItemImageFactory::GetImage(SIIGBF_THUMBNAILONLY)`: 86 87 | Code | Meaning here | 88 |---|---| 89 | `0x8004B200` `WTS_E_FAILEDEXTRACTION` | No handler for the extension, or the handler failed to decode | 90 | `0x80040154` `REGDB_E_CLASSNOTREG` | Handler found but its CLSID is not visible to the host (per-user CLSID in the surrogate) | 91 | `0x8007007E` `ERROR_MOD_NOT_FOUND` | The registered DLL path does not exist (typically a stale HKLM entry) |