| 1 | //go:build windows |
| 2 | |
| 3 | package combridge |
| 4 | |
| 5 | import ( |
| 6 | "fmt" |
| 7 | "reflect" |
| 8 | "sync" |
| 9 | |
| 10 | "golang.org/x/sys/windows" |
| 11 | ) |
| 12 | |
| 13 | var ( |
| 14 | vTablesL sync.Mutex |
| 15 | vTables = make(map[string]*vTable) |
| 16 | ) |
| 17 | |
| 18 | // RegisterVTable registers the vtable trampoline methods for the specified ComInterface |
| 19 | // TBase is the base interface of T, and must be another ComInterface which roots in IUnknown or IUnknown itself. |
| 20 | // The first paramter of the fn is always the uintptr of the ComObject and the GoObject can be resolved with Resolve(). |
| 21 | // After having resolved the GoObject the call must be redirected to the GoObject. |
| 22 | // Typically a trampoline FN looks like this. |
| 23 | // |
| 24 | // func _ICoreWebView2NavigationCompletedEventHandlerInvoke(this uintptr, sender *ICoreWebView2, args *ICoreWebView2NavigationCompletedEventArgs) uintptr { |
| 25 | // return combridge.Resolve[_ICoreWebView2NavigationCompletedEventHandler](this).NavigationCompleted(sender, args) |
| 26 | // } |
| 27 | // |
| 28 | // The order of registration must be in the correct order as specified in the IDL of the interface. |
| 29 | func RegisterVTable[TParent, T IUnknown](guid string, fns ...interface{}) { |
| 30 | registerVTableInternal[TParent, T](guid, false, fns...) |
| 31 | } |
| 32 | |
| 33 | type vTable struct { |
| 34 | Parent *vTable |
| 35 | |
| 36 | Name string |
| 37 | ComGUID string |
| 38 | ComVTable uintptr |
| 39 | ComProcs []uintptr |
| 40 | } |
| 41 | |
| 42 | func registerVTableInternal[TParent, T IUnknown](guid string, isInternal bool, fns ...interface{}) { |
| 43 | vTablesL.Lock() |
| 44 | defer vTablesL.Unlock() |
| 45 | |
| 46 | t, tName := typeInterfaceToString[T]() |
| 47 | tParent, tParentName := typeInterfaceToString[TParent]() |
| 48 | if !t.Implements(tParent) { |
| 49 | panic(fmt.Errorf("RegisterVTable '%s': '%s' must implement '%s'", tName, tName, tParentName)) |
| 50 | } |
| 51 | |
| 52 | if !isInternal { |
| 53 | if t == reflect.TypeOf((*IUnknown)(nil)).Elem() { |
| 54 | panic(fmt.Errorf("RegisterVTable '%s' IUnknown can't be registered", tName)) |
| 55 | } |
| 56 | |
| 57 | if t == tParent { |
| 58 | panic(fmt.Errorf("RegisterVTable '%s': T and TParent can't be the same type", tName)) |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | var parent *vTable |
| 63 | var parentProcs []uintptr |
| 64 | var parentProcsCount int |
| 65 | if t != tParent { |
| 66 | parent = vTables[tParentName] |
| 67 | if parent == nil { |
| 68 | panic(fmt.Errorf("RegisterVTable '%s': Parent VTable '%s' not registered", tName, tParentName)) |
| 69 | } |
| 70 | |
| 71 | parentProcs = parent.ComProcs |
| 72 | parentProcsCount = len(parentProcs) |
| 73 | } |
| 74 | |
| 75 | comGuid, err := windows.GUIDFromString(guid) |
| 76 | if err != nil { |
| 77 | panic(fmt.Errorf("RegisterVTable '%s': invalid guid: %s", tName, err)) |
| 78 | } |
| 79 | |
| 80 | vTable := &vTable{ |
| 81 | Parent: parent, |
| 82 | Name: tName, |
| 83 | ComGUID: comGuid.String(), |
| 84 | } |
| 85 | vTable.ComVTable, vTable.ComProcs = allocUintptrObject(parentProcsCount + len(fns)) |
| 86 | |
| 87 | for i, proc := range parentProcs { |
| 88 | vTable.ComProcs[i] = proc |
| 89 | } |
| 90 | |
| 91 | for i, fn := range fns { |
| 92 | vTable.ComProcs[parentProcsCount+i] = windows.NewCallback(fn) |
| 93 | } |
| 94 | |
| 95 | vTables[tName] = vTable |
| 96 | } |
| 97 | |
| 98 | func typeInterfaceToString[T any]() (reflect.Type, string) { |
| 99 | t := reflect.TypeOf((*T)(nil)) |
| 100 | if t.Kind() != reflect.Pointer { |
| 101 | panic("must be a (*yourInterfaceType)(nil)") |
| 102 | } |
| 103 | t = t.Elem() |
| 104 | return t, t.PkgPath() + "/" + t.Name() |
| 105 | } |
| 106 | |
| 107 | func typeInterfaceToStringOnly[T any]() string { |
| 108 | _, nane := typeInterfaceToString[T]() |
| 109 | return nane |
| 110 | } |
| 111 | |
| 112 | func guidOf[T any]() string { |
| 113 | vtable := vTableOf[T]() |
| 114 | if vtable == nil { |
| 115 | return "" |
| 116 | } |
| 117 | return vtable.ComGUID |
| 118 | } |
| 119 | |
| 120 | func vTableOf[T any]() *vTable { |
| 121 | name := typeInterfaceToStringOnly[T]() |
| 122 | vTablesL.Lock() |
| 123 | defer vTablesL.Unlock() |
| 124 | |
| 125 | return vTables[name] |
| 126 | } |
| 127 | |
| 128 | type ifceImpl interface { |
| 129 | impl() any |
| 130 | ifce() (*vTable, error) |
| 131 | } |
| 132 | |
| 133 | type ifceDef[T any] struct { |
| 134 | objImpl any |
| 135 | } |
| 136 | |
| 137 | func (i ifceDef[T]) impl() any { |
| 138 | return i.objImpl |
| 139 | } |
| 140 | |
| 141 | func (i ifceDef[T]) ifce() (*vTable, error) { |
| 142 | vtable := vTableOf[T]() |
| 143 | if vtable == nil { |
| 144 | return nil, fmt.Errorf("Unable to find vTable for %s", typeInterfaceToStringOnly[T]()) |
| 145 | } |
| 146 | return vtable, nil |
| 147 | } |
| 148 |