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 }