tk_windows.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. // Copyright 2024 The tk9.0-go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package tk9_0 // import "modernc.org/tk9.0"
  5. import (
  6. _ "embed"
  7. "errors"
  8. "fmt"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. "unsafe"
  14. "github.com/evilsocket/islazy/zip"
  15. "golang.org/x/sys/windows"
  16. "modernc.org/memory"
  17. )
  18. // trcw prints and return caller's position and an optional message tagged with TRC. Output goes to stderr.
  19. //
  20. //lint:ignore U1000 debug helper
  21. func trcw(s string, args ...interface{}) string {
  22. switch {
  23. case s == "":
  24. s = fmt.Sprintf(strings.Repeat("%v ", len(args)), args...)
  25. default:
  26. s = fmt.Sprintf(s, args...)
  27. }
  28. r := fmt.Sprintf("%s: TRC(id=%v tid=%v) %s", origin(2), goroutineID(), windows.GetCurrentThreadId(), s)
  29. fmt.Fprintf(os.Stderr, "%s\n", r)
  30. os.Stderr.Sync()
  31. return r
  32. }
  33. var (
  34. // No mutex, the package must be used by a single goroutine only.
  35. allocator memory.Allocator
  36. createCommandProc *windows.Proc
  37. deleteCommandProc *windows.Proc
  38. evalExProc *windows.Proc
  39. getObjResultProc *windows.Proc
  40. getStringProc *windows.Proc
  41. interp uintptr
  42. newStringObjProc *windows.Proc
  43. runCmdProxy = windows.NewCallbackCDecl(eventDispatcher)
  44. setObjResultProc *windows.Proc
  45. splitListProc *windows.Proc
  46. tclDll *windows.DLL
  47. tkDll *windows.DLL
  48. )
  49. func lazyInit() {
  50. if initialized {
  51. return
  52. }
  53. runtime.LockOSThread()
  54. // trcw("LockOSThread")
  55. initialized = true
  56. defer commonLazyInit()
  57. var cacheDir string
  58. if cacheDir, Error = getCacheDir(); Error != nil {
  59. return
  60. }
  61. if bindLibs(cacheDir); Error != nil {
  62. return
  63. }
  64. var nm uintptr
  65. if nm, Error = cString("eventDispatcher"); Error != nil {
  66. return
  67. }
  68. cmd, _, _ := createCommandProc.Call(interp, nm, runCmdProxy, 0, 0)
  69. if cmd == 0 {
  70. Error = fmt.Errorf("registering event dispatcher proxy failed: %v", getObjResultProc)
  71. return
  72. }
  73. setDefaults()
  74. }
  75. func bindLibs(cacheDir string) {
  76. var wd string
  77. if wd, Error = os.Getwd(); Error != nil {
  78. return
  79. }
  80. defer func() {
  81. Error = errors.Join(Error, os.Chdir(wd))
  82. }()
  83. if Error = os.Chdir(cacheDir); Error != nil {
  84. return
  85. }
  86. if tclDll, Error = windows.LoadDLL(tclBin); Error != nil {
  87. return
  88. }
  89. if tkDll, Error = windows.LoadDLL(tkBin); Error != nil {
  90. return
  91. }
  92. var tclCreateInterp, tclInit, tkInit *windows.Proc
  93. if tclCreateInterp, Error = tclDll.FindProc("Tcl_CreateInterp"); Error != nil {
  94. return
  95. }
  96. if tclInit, Error = tclDll.FindProc("Tcl_Init"); Error != nil {
  97. return
  98. }
  99. if createCommandProc, Error = tclDll.FindProc("Tcl_CreateCommand"); Error != nil {
  100. return
  101. }
  102. if deleteCommandProc, Error = tclDll.FindProc("Tcl_DeleteCommand"); Error != nil {
  103. return
  104. }
  105. if evalExProc, Error = tclDll.FindProc("Tcl_EvalEx"); Error != nil {
  106. return
  107. }
  108. if setObjResultProc, Error = tclDll.FindProc("Tcl_SetObjResult"); Error != nil {
  109. return
  110. }
  111. if splitListProc, Error = tclDll.FindProc("Tcl_SplitList"); Error != nil {
  112. return
  113. }
  114. if getObjResultProc, Error = tclDll.FindProc("Tcl_GetObjResult"); Error != nil {
  115. return
  116. }
  117. if getStringProc, Error = tclDll.FindProc("Tcl_GetString"); Error != nil {
  118. return
  119. }
  120. if newStringObjProc, Error = tclDll.FindProc("Tcl_NewStringObj"); Error != nil {
  121. return
  122. }
  123. if tkInit, Error = tkDll.FindProc("Tk_Init"); Error != nil {
  124. return
  125. }
  126. if interp, _, _ = tclCreateInterp.Call(); interp == 0 {
  127. Error = fmt.Errorf("failed to create a Tcl interpreter")
  128. return
  129. }
  130. if r, _, _ := tclInit.Call(interp); r != tcl_ok {
  131. Error = fmt.Errorf("failed to initialize the Tcl interpreter")
  132. return
  133. }
  134. if _, Error := eval(fmt.Sprintf("zipfs mount lib%s.zip /app", libVersion)); Error != nil {
  135. return
  136. }
  137. if r, _, _ := tkInit.Call(interp); r != tcl_ok {
  138. Error = fmt.Errorf("failed to initialize Tk")
  139. return
  140. }
  141. for _, dll := range moreDLLs {
  142. var handle *windows.DLL
  143. if handle, Error = windows.LoadDLL(dll.dll); Error != nil {
  144. return
  145. }
  146. var initProc *windows.Proc
  147. if initProc, Error = handle.FindProc(dll.initProc); Error != nil {
  148. return
  149. }
  150. r, _, _ := initProc.Call(interp)
  151. if r != tcl_ok {
  152. Error = fmt.Errorf("failed to initialize %s: %s", dll.dll, tclResult())
  153. return
  154. }
  155. }
  156. }
  157. func getCacheDir() (r string, err error) {
  158. if r, err = os.UserCacheDir(); err != nil {
  159. return "", err
  160. }
  161. r0 := filepath.Join(r, "modernc.org", libVersion, goos)
  162. r = filepath.Join(r0, goarch)
  163. fi, err := os.Stat(r)
  164. if err == nil && fi.IsDir() {
  165. if checkSig(r, shasig) {
  166. return r, nil
  167. }
  168. os.RemoveAll(r) // Tampered or corrupted.
  169. }
  170. os.MkdirAll(r0, 0700)
  171. tmp, err := os.MkdirTemp(r0, "")
  172. if err != nil {
  173. return "", err
  174. }
  175. zf := filepath.Join(tmp, "lib.zip")
  176. if err = os.WriteFile(zf, libZip, 0660); err != nil {
  177. return "", err
  178. }
  179. if _, err = zip.Unzip(zf, tmp); err != nil {
  180. os.Remove(zf)
  181. return "", err
  182. }
  183. os.Remove(zf)
  184. if err = os.Rename(tmp, r); err == nil {
  185. return r, nil
  186. }
  187. cleanupDirs = append(cleanupDirs, tmp)
  188. return tmp, nil
  189. }
  190. func tclResult() string {
  191. r, _, _ := getObjResultProc.Call(interp)
  192. if r == 0 {
  193. return ""
  194. }
  195. if r, _, _ = getStringProc.Call(r); r != 0 {
  196. return goString(r)
  197. }
  198. return ""
  199. }
  200. func cString(s string) (r uintptr, err error) {
  201. if s == "" {
  202. return 0, nil
  203. }
  204. if r, err = allocator.UintptrMalloc(len(s) + 1); err != nil {
  205. return 0, err
  206. }
  207. copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), len(s)), s)
  208. *(*byte)(unsafe.Add(unsafe.Pointer(r), len(s))) = 0
  209. return r, nil
  210. }
  211. func setResult(s string) (err error) {
  212. cs, err := cString(s)
  213. if err != nil {
  214. return err
  215. }
  216. defer allocator.UintptrFree(cs)
  217. obj, _, _ := newStringObjProc.Call(cs, uintptr(len(s)))
  218. if obj == 0 {
  219. return fmt.Errorf("OOM")
  220. }
  221. setObjResultProc.Call(interp, obj)
  222. return nil
  223. }
  224. func goTransientString(p uintptr) (r string) { // Result cannot be retained.
  225. if p == 0 {
  226. return ""
  227. }
  228. var n uintptr
  229. for p := p; *(*byte)(unsafe.Pointer(p + n)) != 0; n++ {
  230. }
  231. return string(unsafe.Slice((*byte)(unsafe.Pointer(p)), n))
  232. }
  233. func eval(code string) (r string, err error) {
  234. if dmesgs {
  235. defer func() {
  236. dmesg("code=%s -> r=%v err=%v", code, r, err)
  237. }()
  238. }
  239. if !initialized {
  240. lazyInit()
  241. if Error != nil {
  242. return "", Error
  243. }
  244. }
  245. cs, err := cString(code)
  246. if err != nil {
  247. return "", err
  248. }
  249. defer allocator.UintptrFree(cs)
  250. // trcw("code=%s", code)
  251. switch r0, _, _ := evalExProc.Call(interp, cs, uintptr(len(code)), tcl_eval_direct); r0 {
  252. case tcl_ok, tcl_return:
  253. // trcw("%s->%s, nil", code, tclResult())
  254. return tclResult(), nil
  255. default:
  256. // trcw("%s->{}, %s", code, tclResult())
  257. return "", fmt.Errorf("%s", tclResult())
  258. }
  259. }
  260. func eventDispatcher(clientData, in uintptr, argc int32, argv uintptr) uintptr {
  261. if argc < 2 {
  262. setResult(fmt.Sprintf("eventDispatcher internal error: argc=%v", argc))
  263. return tcl_error
  264. }
  265. arg1 := goTransientString(*(*uintptr)(unsafe.Pointer(argv + unsafe.Sizeof(uintptr(0)))))
  266. id, e, err := newEvent(arg1)
  267. if err != nil {
  268. setResult(fmt.Sprintf("eventDispatcher internal error: argv[1]=%q, err=%v", arg1, err))
  269. return tcl_error
  270. }
  271. h := handlers[int32(id)]
  272. e.W = h.w
  273. for i := int32(2); i < argc; i++ {
  274. e.args = append(e.args, goString(*(*uintptr)(unsafe.Pointer(argv + uintptr(i)*unsafe.Sizeof(uintptr(0))))))
  275. }
  276. switch h.callback(e); {
  277. case e.Err != nil:
  278. setResult(tclSafeString(e.Err.Error()))
  279. return tcl_error
  280. default:
  281. if setResult(e.Result) != nil {
  282. return tcl_error
  283. }
  284. return uintptr(e.returnCode)
  285. }
  286. }
  287. // Finalize releases all resources held, if any. This may include temporary
  288. // files. Finalize is intended to be called on process shutdown only.
  289. func Finalize() (err error) {
  290. if finished.Swap(1) != 0 {
  291. return
  292. }
  293. defer runtime.UnlockOSThread()
  294. for _, v := range cleanupDirs {
  295. err = errors.Join(err, os.RemoveAll(v))
  296. }
  297. return err
  298. }
  299. func callSplitList(cList uintptr, argcPtr uintptr, argvPtr uintptr) (r1 uintptr, r2 uintptr, err error) {
  300. return splitListProc.Call(interp, cList, argcPtr, argvPtr)
  301. }
  302. // Internal malloc enabling parseList() in tk.go to not care about the target
  303. // specific implemetations.
  304. func malloc(sz int) (r uintptr, err error) {
  305. return allocator.UintptrMalloc(sz)
  306. }
  307. // Internal free enabling parseList() in tk.go to not care about the target
  308. // specific implemetations.
  309. func free(p uintptr) (err error) {
  310. return allocator.UintptrFree(p)
  311. }