icns

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

com.go (5253B)


      1 //go:build windows
      2 
      3 // Package com contains the minimal set of COM definitions needed to implement
      4 // an in-process COM server in pure Go: GUIDs, HRESULT codes, the IUnknown and
      5 // IClassFactory vtables, and an [io.Reader] adapter over IStream.
      6 //
      7 // A COM interface pointer points at a struct whose first word is a pointer to
      8 // a vtable: a struct of function pointers. To implement an interface in Go we
      9 // allocate a vtable populated with [syscall.NewCallback] trampolines and hand
     10 // COM a pointer to a Go struct whose first field is that vtable pointer. Each
     11 // trampoline receives the struct pointer back as its `this` argument.
     12 //
     13 // Callbacks created by [syscall.NewCallback] are never freed, so every vtable
     14 // is a package-level singleton shared by all instances of an interface.
     15 package com
     16 
     17 import (
     18 	"fmt"
     19 	"io"
     20 	"math"
     21 	"syscall"
     22 	"unsafe"
     23 
     24 	"golang.org/x/sys/windows"
     25 )
     26 
     27 // GUID identifies COM classes (CLSID) and interfaces (IID).
     28 type GUID = windows.GUID
     29 
     30 // MustGUID parses a GUID of the form "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}",
     31 // panicking on malformed input. Intended for package-level constants.
     32 func MustGUID(s string) *GUID {
     33 	g, err := windows.GUIDFromString(s)
     34 	if err != nil {
     35 		panic(fmt.Sprintf("com: invalid GUID %q: %v", s, err))
     36 	}
     37 	return &g
     38 }
     39 
     40 // IsEqualGUID reports whether two GUIDs are equal. Nil never equals anything.
     41 func IsEqualGUID(a, b *GUID) bool {
     42 	return a != nil && b != nil && *a == *b
     43 }
     44 
     45 // HRESULT is a COM status code.
     46 //
     47 // It is sized as uintptr rather than the 32-bit LONG that COM defines because
     48 // [syscall.NewCallback] requires callbacks to return exactly one uintptr-sized
     49 // value. Callers only read the low 32 bits.
     50 type HRESULT = uintptr
     51 
     52 // Well-known HRESULT values.
     53 const (
     54 	S_OK    HRESULT = 0x00000000
     55 	S_FALSE HRESULT = 0x00000001
     56 
     57 	E_NOTIMPL     HRESULT = 0x80004001
     58 	E_NOINTERFACE HRESULT = 0x80004002
     59 	E_POINTER     HRESULT = 0x80004003
     60 	E_FAIL        HRESULT = 0x80004005
     61 	E_UNEXPECTED  HRESULT = 0x8000FFFF
     62 	E_OUTOFMEMORY HRESULT = 0x8007000E
     63 	E_INVALIDARG  HRESULT = 0x80070057
     64 
     65 	CLASS_E_NOAGGREGATION     HRESULT = 0x80040110
     66 	CLASS_E_CLASSNOTAVAILABLE HRESULT = 0x80040111
     67 
     68 	// HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED): the documented result of
     69 	// IInitializeWithStream::Initialize when called a second time.
     70 	HRESULT_ALREADY_INITIALIZED HRESULT = 0x800704DF
     71 
     72 	// WTS_E_FAILEDEXTRACTION signals that a thumbnail could not be produced.
     73 	WTS_E_FAILEDEXTRACTION HRESULT = 0x8004B200
     74 )
     75 
     76 // Failed reports whether hr denotes failure (the sign bit of the 32-bit code).
     77 func Failed(hr HRESULT) bool {
     78 	return int32(uint32(hr)) < 0
     79 }
     80 
     81 // Error wraps a failing HRESULT as a Go error.
     82 type Error HRESULT
     83 
     84 func (e Error) Error() string {
     85 	return fmt.Sprintf("HRESULT 0x%08X", uint32(e))
     86 }
     87 
     88 // Well-known interface identifiers.
     89 var (
     90 	IID_IUnknown      = MustGUID("{00000000-0000-0000-C000-000000000046}")
     91 	IID_IClassFactory = MustGUID("{00000001-0000-0000-C000-000000000046}")
     92 	IID_IStream       = MustGUID("{0000000C-0000-0000-C000-000000000046}")
     93 )
     94 
     95 // IUnknownVtbl is the vtable every COM interface begins with.
     96 type IUnknownVtbl struct {
     97 	QueryInterface uintptr // HRESULT (*)(This, REFIID riid, void **ppvObject)
     98 	AddRef         uintptr // ULONG   (*)(This)
     99 	Release        uintptr // ULONG   (*)(This)
    100 }
    101 
    102 // IClassFactoryVtbl is the vtable of IClassFactory.
    103 type IClassFactoryVtbl struct {
    104 	IUnknownVtbl
    105 	CreateInstance uintptr // HRESULT (*)(This, IUnknown *pUnkOuter, REFIID riid, void **ppvObject)
    106 	LockServer     uintptr // HRESULT (*)(This, BOOL fLock)
    107 }
    108 
    109 // IStreamVtbl is the vtable of IStream (which extends ISequentialStream).
    110 type IStreamVtbl struct {
    111 	IUnknownVtbl
    112 	Read         uintptr // HRESULT (*)(This, void *pv, ULONG cb, ULONG *pcbRead)
    113 	Write        uintptr
    114 	Seek         uintptr
    115 	SetSize      uintptr
    116 	CopyTo       uintptr
    117 	Commit       uintptr
    118 	Revert       uintptr
    119 	LockRegion   uintptr
    120 	UnlockRegion uintptr
    121 	Stat         uintptr
    122 	Clone        uintptr
    123 }
    124 
    125 // IStream is a COM stream owned by the caller. It implements [io.Reader] so
    126 // COM-provided data can be consumed by ordinary Go decoders.
    127 type IStream struct {
    128 	vtbl *IStreamVtbl
    129 }
    130 
    131 var _ io.Reader = (*IStream)(nil)
    132 
    133 // Read implements [io.Reader] over IStream::Read.
    134 func (s *IStream) Read(p []byte) (int, error) {
    135 	if len(p) == 0 {
    136 		return 0, nil
    137 	}
    138 	if len(p) > math.MaxUint32 {
    139 		p = p[:math.MaxUint32]
    140 	}
    141 	var n uint32
    142 	r, _, _ := syscall.SyscallN(
    143 		s.vtbl.Read,
    144 		uintptr(unsafe.Pointer(s)),
    145 		uintptr(unsafe.Pointer(unsafe.SliceData(p))),
    146 		uintptr(len(p)),
    147 		uintptr(unsafe.Pointer(&n)),
    148 	)
    149 	// Only the low 32 bits of the return register hold the HRESULT.
    150 	hr := HRESULT(uint32(r))
    151 	if Failed(hr) {
    152 		return int(n), Error(hr)
    153 	}
    154 	// IStream::Read reports end of stream either with S_FALSE or with S_OK
    155 	// and zero bytes read, depending on the implementation.
    156 	if n == 0 {
    157 		return 0, io.EOF
    158 	}
    159 	return int(n), nil
    160 }
    161 
    162 // AddRef increments the stream's reference count.
    163 func (s *IStream) AddRef() uint32 {
    164 	r, _, _ := syscall.SyscallN(s.vtbl.AddRef, uintptr(unsafe.Pointer(s)))
    165 	return uint32(r)
    166 }
    167 
    168 // Release decrements the stream's reference count.
    169 func (s *IStream) Release() uint32 {
    170 	r, _, _ := syscall.SyscallN(s.vtbl.Release, uintptr(unsafe.Pointer(s)))
    171 	return uint32(r)
    172 }