factory.go (3191B)
1 //go:build windows 2 3 package com 4 5 import ( 6 "log/slog" 7 "runtime" 8 "runtime/debug" 9 "syscall" 10 "unsafe" 11 ) 12 13 // Constructor creates a new object and stores the interface identified by riid 14 // in *ppv, returning E_NOINTERFACE when the object does not implement riid. 15 type Constructor func(riid *GUID, ppv *unsafe.Pointer) HRESULT 16 17 // ClassFactory implements IClassFactory for a single COM class. 18 // 19 // A ClassFactory is a process-lifetime singleton: AddRef and Release are 20 // no-ops and the object is pinned so COM may hold its pointer indefinitely. 21 // The vtable pointer must remain the first field. 22 type ClassFactory struct { 23 vtbl *IClassFactoryVtbl 24 create Constructor 25 pin runtime.Pinner 26 } 27 28 // NewClassFactory returns a factory whose CreateInstance defers to create. 29 func NewClassFactory(create Constructor) *ClassFactory { 30 f := &ClassFactory{vtbl: classFactoryVtbl, create: create} 31 f.pin.Pin(f) 32 return f 33 } 34 35 // QueryInterface implements IUnknown::QueryInterface for the factory. 36 func (f *ClassFactory) QueryInterface(riid *GUID, ppv *unsafe.Pointer) HRESULT { 37 if ppv == nil { 38 return E_POINTER 39 } 40 if !IsEqualGUID(riid, IID_IUnknown) && !IsEqualGUID(riid, IID_IClassFactory) { 41 *ppv = nil 42 return E_NOINTERFACE 43 } 44 *ppv = unsafe.Pointer(f) 45 return S_OK 46 } 47 48 // CreateInstance implements IClassFactory::CreateInstance. 49 func (f *ClassFactory) CreateInstance(outer unsafe.Pointer, riid *GUID, ppv *unsafe.Pointer) HRESULT { 50 if ppv == nil { 51 return E_POINTER 52 } 53 *ppv = nil 54 if outer != nil { 55 return CLASS_E_NOAGGREGATION 56 } 57 return f.create(riid, ppv) 58 } 59 60 // Guard runs a COM method body and converts a panic into E_FAIL. 61 // 62 // A panic that unwinds out of a syscall.NewCallback trampoline crosses C 63 // frames and takes the host process down with it, which in the shell's case 64 // means Explorer. COM callers expect failures as HRESULTs, so report it as one. 65 func Guard(method string, body func() HRESULT) (hr HRESULT) { 66 defer func() { 67 if p := recover(); p != nil { 68 slog.Error("panic in COM method", "method", method, "panic", p, "stack", string(debug.Stack())) 69 hr = E_FAIL 70 } 71 }() 72 return body() 73 } 74 75 // classFactoryVtbl is shared by all factories. The trampolines recover the 76 // concrete factory from the `this` pointer COM passes back to us. 77 var classFactoryVtbl = &IClassFactoryVtbl{ 78 IUnknownVtbl: IUnknownVtbl{ 79 QueryInterface: syscall.NewCallback(func(this *ClassFactory, riid *GUID, ppv *unsafe.Pointer) uintptr { 80 return Guard("IClassFactory::QueryInterface", func() HRESULT { return this.QueryInterface(riid, ppv) }) 81 }), 82 AddRef: syscall.NewCallback(func(this *ClassFactory) uintptr { 83 return 1 84 }), 85 Release: syscall.NewCallback(func(this *ClassFactory) uintptr { 86 return 1 87 }), 88 }, 89 CreateInstance: syscall.NewCallback(func(this *ClassFactory, outer unsafe.Pointer, riid *GUID, ppv *unsafe.Pointer) uintptr { 90 return Guard("IClassFactory::CreateInstance", func() HRESULT { return this.CreateInstance(outer, riid, ppv) }) 91 }), 92 LockServer: syscall.NewCallback(func(this *ClassFactory, lock uintptr) uintptr { 93 // The Go runtime can never be unloaded from a host process (see 94 // DllCanUnloadNow), so there is nothing to lock. 95 return S_OK 96 }), 97 }