// SPDX-License-Identifier: Apache-2.0 // // Copyright The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //go:build windows && (amd64 || arm64) package ole import ( "errors" "fmt" "runtime" "strings" "syscall" "unicode/utf16" "unicode/utf8" "unsafe" "golang.org/x/sys/windows" ) //nolint:gochecknoglobals var ( ole32 = windows.NewLazySystemDLL("ole32.dll") oleaut32 = windows.NewLazySystemDLL("oleaut32.dll") coInitializeEx = ole32.NewProc("CoInitializeEx") coUninitialize = ole32.NewProc("CoUninitialize") coCreateInstance = ole32.NewProc("CoCreateInstance") sysAllocStringLen = oleaut32.NewProc("SysAllocStringLen") sysFreeString = oleaut32.NewProc("SysFreeString") sysStringLen = oleaut32.NewProc("SysStringLen") ) // Initialize initializes the MTA and disables legacy OLE 1.0 DDE. The caller // must lock its OS thread before calling and release all objects before // Uninitialize on that same thread. S_FALSE is successful initialization. func Initialize() error { hr, _, _ := coInitializeEx.Call(0, windows.COINIT_MULTITHREADED|windows.COINIT_DISABLE_OLE1DDE) if err := ResultError(hr); err != nil { return fmt.Errorf("initialize COM: %w", err) } return nil } // Uninitialize balances a successful Initialize on the same locked OS thread. // Release all owned COM interfaces before calling it. func Uninitialize() { // CoUninitialize returns void; GetLastError is irrelevant. _, _, _ = coUninitialize.Call() } // Call invokes a native COM method and returns its primary result. COM methods // report HRESULTs or reference counts directly; GetLastError is irrelevant. // Convert pointers to uintptr in the call expression so uintptrescapes moves // their storage to the heap, where reentrant Go callbacks cannot invalidate // native pointers by growing and moving the goroutine stack. // //go:uintptrescapes func Call(fn uintptr, args ...uintptr) uintptr { result, _, _ := syscall.SyscallN(fn, args...) return result } // Object is the common native COM interface layout. VTable points to the native // function table and is owned by COM. Object must be the first field of every // interface wrapper; never copy it or move it between apartments. type Object struct{ VTable *uintptr } // nativeScalar includes only output layouts used by these COM bindings. A Go // bool is deliberately excluded: COM VARIANT_BOOL uses a signed 16-bit integer. type nativeScalar interface { ~int16 | ~int32 | ~uint32 | ~float64 | bstr } // nativeObject requires the common COM interface layout without extra Go data. type nativeObject interface{ ~struct{ Object } } // Method returns the address of the zero-based native vtable slot. func (o *Object) Method(slot uintptr) uintptr { return *(*uintptr)(unsafe.Add(unsafe.Pointer(o.VTable), slot*unsafe.Sizeof(uintptr(0)))) } // Release relinquishes an owned interface reference. Do not call it on borrowed // iterator items. Like IUnknown::Release, it does not return an HRESULT. func (o *Object) Release() { _ = Call(o.Method(2), uintptr(unsafe.Pointer(o))) runtime.KeepAlive(o) } // Get calls a native scalar getter. T must exactly match its native // output type. Pointer conversions stay in the Call expression so their storage // remains alive and stable during native calls and reentrant Go callbacks. // //nolint:ireturn // T is a native scalar output, not a Go interface. func (o *Object) Get[T nativeScalar](slot uintptr) (T, error) { var value T hr := Call(o.Method(slot), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(&value))) runtime.KeepAlive(o) return value, ResultError(hr) } // GetArg calls a native getter with one input argument. T must exactly match // its native output type; arg must use the native argument representation. // //nolint:ireturn // T is a native scalar output, not a Go interface. //go:uintptrescapes func (o *Object) GetArg[T nativeScalar](slot, arg uintptr) (T, error) { var value T hr := Call( o.Method(slot), uintptr(unsafe.Pointer(o)), arg, uintptr(unsafe.Pointer(&value)), ) runtime.KeepAlive(o) return value, ResultError(hr) } // GetObject calls a native interface getter and returns an owned reference. // Successful calls must return a non-nil interface pointer. func (o *Object) GetObject[T nativeObject](slot uintptr) (*T, error) { var value *T hr := Call(o.Method(slot), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(&value))) runtime.KeepAlive(o) if err := ResultError(hr); err != nil { return nil, err } if value == nil { return nil, fmt.Errorf("COM getter at slot %d returned a nil interface", slot) } return value, nil } // GetObjectArg calls a native interface getter with one input argument and // returns an owned reference. The argument must use its native representation. // Convert pointer arguments to uintptr in the call expression. // //go:uintptrescapes func (o *Object) GetObjectArg[T nativeObject](slot, arg uintptr) (*T, error) { var value *T hr := Call( o.Method(slot), uintptr(unsafe.Pointer(o)), arg, uintptr(unsafe.Pointer(&value)), ) runtime.KeepAlive(o) if err := ResultError(hr); err != nil { return nil, err } if value == nil { return nil, fmt.Errorf("COM getter at slot %d returned a nil interface", slot) } return value, nil } // Put calls a native setter with a scalar argument. func (o *Object) Put(slot, arg uintptr) error { hr := Call(o.Method(slot), uintptr(unsafe.Pointer(o)), arg) runtime.KeepAlive(o) return ResultError(hr) } // Create activates class and returns an owned interface reference for iid. // T must be the matching native interface wrapper, with Object as its first field. func Create[T nativeObject](class, iid windows.GUID) (*T, error) { var value *T hr, _, _ := coCreateInstance.Call( uintptr(unsafe.Pointer(&class)), 0, 0x1|0x4, // CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER uintptr(unsafe.Pointer(&iid)), uintptr(unsafe.Pointer(&value)), ) if err := ResultError(hr); err != nil { return nil, fmt.Errorf("create COM instance: %w", err) } if value == nil { return nil, errors.New("create COM instance returned a nil interface") } return value, nil } type bstr struct{ ptr *uint16 } func newBSTR(value string) (bstr, error) { units := utf16.Encode([]rune(value)) // Keep a non-nil input buffer even for an empty string. units = append(units, 0) p, _, _ := sysAllocStringLen.Call(uintptr(unsafe.Pointer(&units[0])), uintptr(len(units)-1)) if p == 0 { return bstr{}, fmt.Errorf("allocate BSTR: %w", HRESULT(0x8007000e)) } return bstr{ptr: (*uint16)(unsafe.Pointer(p))}, nil } func (b bstr) free() { // SysFreeString returns void and accepts a nil BSTR. _, _, _ = sysFreeString.Call(uintptr(unsafe.Pointer(b.ptr))) } func (b bstr) string() string { if b.ptr == nil { return "" } n, _, _ := sysStringLen.Call(uintptr(unsafe.Pointer(b.ptr))) units := unsafe.Slice(b.ptr, int(n)) for _, unit := range units { if unit >= utf8.RuneSelf { return string(utf16.Decode(units)) } } // ASCII needs no decoding, so copy it without an intermediate rune slice. var value strings.Builder value.Grow(len(units)) for _, unit := range units { value.WriteByte(byte(unit)) } return value.String() } // String calls a BSTR getter, copies its value, and frees the native string. func (o *Object) String(slot uintptr) (string, error) { value, err := o.Get[bstr](slot) defer value.free() if err != nil { return "", err } return value.string(), nil } // Variant is the 24-byte VARIANT layout on both supported Windows architectures. // Native 64-bit ABIs pass this aggregate by address. Only VT_EMPTY and VT_I4 // are needed for local Connect and one-based Task Scheduler collection indices. type Variant struct { Type uint16 reserved [3]uint16 Value int64 padding [8]byte } // The x64 ABI requires caller-owned aggregate arguments to be 16-byte aligned. // Go only guarantees eight-byte alignment for VARIANT, so reserve padding and // align explicitly. The same storage is also valid on arm64. type variantStorage [39]byte func (s *variantStorage) variant() *Variant { offset := -uintptr(unsafe.Pointer(s)) & 15 return (*Variant)(unsafe.Add(unsafe.Pointer(s), offset)) } // GetObjectStringArg calls a native interface getter with one BSTR input and // returns an owned reference. The temporary BSTR is freed before returning. func (o *Object) GetObjectStringArg[T nativeObject](slot uintptr, input string) (*T, error) { value, err := newBSTR(input) if err != nil { return nil, err } defer value.free() return o.GetObjectArg[T](slot, uintptr(unsafe.Pointer(value.ptr))) } // PutString calls a setter with a BSTR input, freeing the temporary string. func (o *Object) PutString(slot uintptr, input string) error { value, err := newBSTR(input) if err != nil { return err } defer value.free() return o.Put(slot, uintptr(unsafe.Pointer(value.ptr))) } // NewEmptyVariant returns caller-owned, 16-byte-aligned VT_EMPTY storage. // It contains no resources requiring VariantClear. func NewEmptyVariant() *Variant { return new(variantStorage).variant() } // NewInt32Variant returns caller-owned, 16-byte-aligned VT_I4 storage. // It contains no resources requiring VariantClear. func NewInt32Variant(value int32) *Variant { v := NewEmptyVariant() v.Type = 3 v.Value = int64(value) return v }