const GUID{{.Name}} string = "{{.GUID}}" const Signature{{.Name}} string = "{{.Signature}}" type {{.Name}} struct { ole.IUnknown sync.Mutex refs uintptr IID ole.GUID } type {{.Name}}Vtbl struct { ole.IUnknownVtbl Invoke uintptr } type {{.Name}}Callback func(instance *{{.Name}},{{- range .InParams -}} {{.GoVarName}} {{template "variabletype.tmpl" . }}, {{- end -}}) var callbacks{{.Name}} = &{{.Name | toLower}}Callbacks { mu: &sync.Mutex{}, callbacks: make(map[unsafe.Pointer]{{.Name}}Callback), } var releaseChannels{{.Name}} = &{{.Name | toLower}}ReleaseChannels { mu: &sync.Mutex{}, chans: make(map[unsafe.Pointer]chan struct{}), } func New{{.Name}}(iid *ole.GUID, callback {{.Name}}Callback) *{{.Name}} { // create type instance size := unsafe.Sizeof(*(*{{.Name}})(nil)) instPtr := kernel32.Malloc(size) inst := (*{{.Name}})(instPtr) // get the callbacks for the VTable callbacks := delegate.RegisterCallbacks(instPtr, inst) // the VTable should also be allocated in the heap sizeVTable := unsafe.Sizeof(*(*{{.Name}}Vtbl)(nil)) vTablePtr := kernel32.Malloc(sizeVTable) inst.RawVTable = (*interface{})(vTablePtr) vTable := (*{{.Name}}Vtbl)(vTablePtr) vTable.IUnknownVtbl = ole.IUnknownVtbl{ QueryInterface: callbacks.QueryInterface, AddRef: callbacks.AddRef, Release: callbacks.Release, } vTable.Invoke = callbacks.Invoke // Initialize all properties: the malloc may contain garbage inst.IID = *iid // copy contents inst.Mutex = sync.Mutex{} inst.refs = 0 callbacks{{.Name}}.add(unsafe.Pointer(inst), callback) // See the docs in the releaseChannels{{.Name}} struct releaseChannels{{.Name}}.acquire(unsafe.Pointer(inst)) inst.addRef() return inst } func (r *{{.Name}}) GetIID() *ole.GUID { return &r.IID } // addRef increments the reference counter by one func (r *{{.Name}}) addRef() uintptr { r.Lock() defer r.Unlock() r.refs++ return r.refs } // removeRef decrements the reference counter by one. If it was already zero, it will just return zero. func (r *{{.Name}}) removeRef() uintptr { r.Lock() defer r.Unlock() if r.refs > 0 { r.refs-- } return r.refs } func (instance *{{.Name}}) Invoke(instancePtr, rawArgs0, rawArgs1, rawArgs2, rawArgs3, rawArgs4, rawArgs5, rawArgs6, rawArgs7, rawArgs8 unsafe.Pointer) uintptr { {{range $i, $arg := .InParams -}} {{- if $arg.Type.IsEnum -}} {{$arg.GoVarName}}Raw := ({{$arg.Type.UnderlyingEnumType}})(uintptr(rawArgs{{$i}})) {{- else -}} {{$arg.GoVarName}}Ptr := rawArgs{{$i}} {{- end}} {{end}} // See the quote above. {{range .InParams -}} {{if .Type.IsEnum -}} {{.GoVarName}} := ({{template "variabletype.tmpl" . }})({{.GoVarName}}Raw) {{else -}} {{.GoVarName}} := ({{template "variabletype.tmpl" . }})({{.GoVarName}}Ptr) {{end -}} {{end -}} if callback, ok := callbacks{{.Name}}.get(instancePtr); ok { callback(instance, {{range .InParams}}{{.GoVarName}},{{end}}) } return ole.S_OK } func (instance *{{.Name}}) AddRef() uintptr { return instance.addRef() } func (instance *{{.Name}}) Release() uintptr { rem := instance.removeRef() if rem == 0 { // We're done. instancePtr := unsafe.Pointer(instance) callbacks{{.Name}}.delete(instancePtr) // stop release channels used to avoid // https://github.com/golang/go/issues/55015 releaseChannels{{.Name}}.release(instancePtr) kernel32.Free(unsafe.Pointer(instance.RawVTable)) kernel32.Free(instancePtr) } return rem } type {{.Name | toLower}}Callbacks struct { mu *sync.Mutex callbacks map[unsafe.Pointer]{{.Name}}Callback } func (m *{{.Name | toLower}}Callbacks) add(p unsafe.Pointer, v {{.Name}}Callback) { m.mu.Lock() defer m.mu.Unlock() m.callbacks[p] = v } func (m *{{.Name | toLower}}Callbacks) get(p unsafe.Pointer) ({{.Name}}Callback, bool) { m.mu.Lock() defer m.mu.Unlock() v, ok := m.callbacks[p] return v, ok } func (m *{{.Name | toLower}}Callbacks) delete(p unsafe.Pointer) { m.mu.Lock() defer m.mu.Unlock() delete(m.callbacks, p) } // typedEventHandlerReleaseChannels keeps a map with channels // used to keep a goroutine alive during the lifecycle of this object. // This is required to avoid causing a deadlock error. // See this: https://github.com/golang/go/issues/55015 type {{.Name | toLower}}ReleaseChannels struct { mu *sync.Mutex chans map[unsafe.Pointer]chan struct{} } func (m *{{.Name | toLower}}ReleaseChannels) acquire(p unsafe.Pointer) { m.mu.Lock() defer m.mu.Unlock() c := make(chan struct{}) m.chans[p] = c go func() { // we need a timer to trick the go runtime into // thinking there's still something going on here // but we are only really interested in <-c t := time.NewTimer(time.Minute) for { select { case <-t.C: t.Reset(time.Minute) case <-c: t.Stop() return } } }() } func (m *{{.Name | toLower}}ReleaseChannels) release(p unsafe.Pointer) { m.mu.Lock() defer m.mu.Unlock() if c, ok := m.chans[p]; ok { close(c) delete(m.chans, p) } }