rum-goggles/vendor/github.com/wailsapp/go-webview2/pkg/combridge/bridge.go

240 lines
5.6 KiB
Go
Raw Normal View History

2024-02-23 16:39:16 +00:00
//go:build windows
package combridge
import (
"fmt"
"runtime"
"sync"
"sync/atomic"
)
var (
comIfcePointersL sync.RWMutex
comIfcePointers = map[uintptr]*comObject{} // Map from ComInterfacePointer to the Go ComObject
)
// Resolve the GoInterface of the specified ComInterfacePointer
func Resolve[T IUnknown](ifceP uintptr) T {
comIfcePointersL.RLock()
comObj := comIfcePointers[ifceP]
comIfcePointersL.RUnlock()
var n T
if comObj != nil {
t := comObj.resolve(ifceP)
if t != nil {
n = t.(T)
}
}
return n
}
// New returns a new ComObject which implements the specified Com Interface, com calls will be redirected
// to the specified go interface.
func New[T IUnknown](obj T) *ComObject[T] {
cObj := new(
ifceDef[T]{obj},
)
return newComObject[T](cObj)
}
// New2 returns a new ComObject which implements the two specified Com Interfaces, com calls will be redirected
// to those interfaces accordingly.
// This is needed if a ComObject should implement two interfaces that are not descendants of each other,
// then you get multiple inheritance.
func New2[T IUnknown, T2 IUnknown](obj T, obj2 T2) *ComObject[T] {
cObj := new(
ifceDef[T]{obj},
ifceDef[T2]{obj2},
)
return newComObject[T](cObj)
}
// new returns a new ComObject which implements multiple specified Com Interfaces, com calls will be redirected
// to the specified go interfaces accordingly.
// This is needed if a ComObject should implement multiple interfaces that are not descendants of each other,
// then you get multiple inheritance.
func new(impls ...ifceImpl) *comObject {
impls = append([]ifceImpl{ifceDef[IUnknown]{}}, impls...)
cObj := &comObject{
refCount: 1,
ifces: map[string]int{},
ifcesImpl: make([]comInterfaceDesc, len(impls)),
}
for i, ifceDef := range impls {
vtable, err := ifceDef.ifce()
if err != nil {
panic(err)
}
needsImplement := false
for table := vtable; table != nil; table = table.Parent {
guid := table.ComGUID
if i, found := cObj.ifces[guid]; found {
// This Interface is already implemented
if guid == iUnknownGUID {
// IUnknown is a special interface and never has an user specific implementation
} else if cObj.ifcesImpl[i].impl != ifceDef.impl() {
panic(fmt.Sprintf("Interface '%s' is already implemented by another object", table.Name))
}
break
}
needsImplement = true
cObj.ifces[guid] = i
}
if !needsImplement {
continue
}
ifceP, ifcePSlice := allocUintptrObject(1)
ifcePSlice[0] = vtable.ComVTable
cObj.ifcesImpl[i] = comInterfaceDesc{ifceP, ifceDef.impl()}
}
comIfcePointersL.Lock()
for _, ifceImpl := range cObj.ifcesImpl {
comIfcePointers[ifceImpl.ref] = cObj
}
comIfcePointersL.Unlock()
return cObj
}
func newComObject[T IUnknown](comObj *comObject) *ComObject[T] {
c := &ComObject[T]{obj: comObj}
// Make sure to async release since release needs locks and might block the finalizer goroutine for a longer period
runtime.SetFinalizer(c, func(obj *ComObject[T]) { obj.close(true) })
return c
}
// ComObject describes an exported go instance to be used as a ComObject which implements
// the specified Interface.
type ComObject[T IUnknown] struct {
obj *comObject
closed int32
}
// Ref returns the native uintptr that points to the ComObject that is an interface pointer to T.
// This can be used in native calls. If the object has been closed this function will panic.
func (o *ComObject[T]) Ref() uintptr {
if atomic.LoadInt32(&o.closed) != 0 {
panic("ComObject has been released")
}
return o.obj.queryInterface(guidOf[T](), false)
}
// Close releases the native com object from the go side. It will only be destroyed if the ref counter
// reaches zero.
// After closing `Ref()` will panic.
func (o *ComObject[T]) Close() error {
o.close(false)
return nil
}
// close releases the native com object from the go side. It will only be destroyed if the ref counter
// reaches zero.
// After closing `Ref()` will panic.
func (o *ComObject[T]) close(asyncRelease bool) {
if atomic.CompareAndSwapInt32(&o.closed, 0, 1) {
runtime.SetFinalizer(o, nil)
if asyncRelease {
go o.obj.release()
} else {
o.obj.release()
}
}
}
type comInterfaceDesc struct {
ref uintptr // The native Com InterfacePointer
impl any // The golang target object
}
type comObject struct {
l sync.Mutex
refCount int32
ifces map[string]int // Map of ComInterfaceGUID to Interface Slots
ifcesImpl []comInterfaceDesc // Slots with InterfaceDescriptors
}
func (c *comObject) queryInterface(ifceGUID string, withAddRef bool) uintptr {
c.l.Lock()
defer c.l.Unlock()
if c.refCount <= 0 {
panic("call on released com object")
}
i, found := c.ifces[ifceGUID]
if !found {
return 0
}
if withAddRef {
c.refCount++
}
return c.ifcesImpl[i].ref
}
func (c *comObject) resolve(ifceP uintptr) any {
c.l.Lock()
defer c.l.Unlock()
if c.refCount <= 0 {
panic("call on destroyed com object")
}
for _, ifce := range c.ifcesImpl {
if ifce.ref != ifceP {
continue
}
return ifce.impl
}
return nil
}
func (c *comObject) addRef() int32 {
c.l.Lock()
defer c.l.Unlock()
if c.refCount <= 0 {
panic("call on destroyed com object")
}
c.refCount++
return c.refCount
}
func (c *comObject) release() int32 {
c.l.Lock()
defer c.l.Unlock()
if c.refCount <= 0 {
panic("call on destroyed com object")
}
if c.refCount--; c.refCount == 0 {
comIfcePointersL.Lock()
for _, ref := range c.ifcesImpl {
delete(comIfcePointers, ref.ref)
}
comIfcePointersL.Unlock()
for _, impl := range c.ifcesImpl {
ref := impl.ref
if ref == 0 {
continue
}
globalFree(ref)
}
}
return c.refCount
}