icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

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 }