main.go (2747B)
1 //go:build windows 2 3 // Command shell-extension builds a Windows shell extension DLL that lets 4 // Explorer render thumbnails for .icns files. 5 // 6 // The DLL is an in-process COM server implemented entirely in Go. cgo is used 7 // only to export the four entry points COM requires from any such server; the 8 // COM objects themselves live in the internal/provider and internal/com 9 // packages and contain no C. 10 // 11 // Build and register (per-user, no elevation needed): 12 // 13 // go build -buildmode=c-shared -o icns-shellext.dll . 14 // regsvr32 icns-shellext.dll 15 // 16 // Unregister with `regsvr32 /u icns-shellext.dll`. 17 package main 18 19 import "C" 20 21 import ( 22 "unsafe" 23 24 "github.com/jackmordaunt/icns/cmd/shell-extension/internal/com" 25 "github.com/jackmordaunt/icns/cmd/shell-extension/internal/provider" 26 ) 27 28 // main is required by -buildmode=c-shared but never runs. 29 func main() {} 30 31 // factory builds thumbnail providers for the shell. 32 var factory = com.NewClassFactory(provider.Create) 33 34 // DllGetClassObject hands out the class factory for our CLSID. 35 // 36 //export DllGetClassObject 37 func DllGetClassObject(rclsid, riid unsafe.Pointer, ppv *unsafe.Pointer) uint32 { 38 return uint32(com.Guard("DllGetClassObject", func() com.HRESULT { 39 if ppv == nil { 40 return com.E_POINTER 41 } 42 *ppv = nil 43 if !com.IsEqualGUID((*com.GUID)(rclsid), provider.CLSID) { 44 return com.CLASS_E_CLASSNOTAVAILABLE 45 } 46 return factory.QueryInterface((*com.GUID)(riid), ppv) 47 })) 48 } 49 50 // DllCanUnloadNow always refuses: a Go runtime cannot be torn down and 51 // unloaded from a host process, so the DLL must stay resident until the host 52 // exits. Thumbnail providers run in a dedicated surrogate process that exits 53 // when idle, so this costs nothing in Explorer itself. 54 // 55 //export DllCanUnloadNow 56 func DllCanUnloadNow() uint32 { 57 return uint32(com.S_FALSE) 58 } 59 60 // DllRegisterServer registers the provider for the current user. 61 // 62 //export DllRegisterServer 63 func DllRegisterServer() uint32 { 64 return uint32(com.Guard("DllRegisterServer", func() com.HRESULT { 65 if err := register(); err != nil { 66 logError("registering", err) 67 return com.E_FAIL 68 } 69 return com.S_OK 70 })) 71 } 72 73 // DllUnregisterServer removes the current user's registration. 74 // 75 //export DllUnregisterServer 76 func DllUnregisterServer() uint32 { 77 return uint32(com.Guard("DllUnregisterServer", func() com.HRESULT { 78 if err := unregister(); err != nil { 79 logError("unregistering", err) 80 return com.E_FAIL 81 } 82 return com.S_OK 83 })) 84 } 85 86 // DllInstall supports `regsvr32 /n /i[:cmdline]`. Registration is always 87 // per-user, so the command line is ignored. 88 // 89 //export DllInstall 90 func DllInstall(install int32, cmdline unsafe.Pointer) uint32 { 91 if install != 0 { 92 return DllRegisterServer() 93 } 94 return DllUnregisterServer() 95 }