240 lines
5.6 KiB
Go
240 lines
5.6 KiB
Go
|
//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
|
||
|
}
|