Golang中如何同时运行多个Webview实例

Golang中如何同时运行多个Webview实例 我正在尝试编写一个Go应用程序,能够显示多个承载小型JavaScript应用程序的Webview(github.com/webview/webview),可以想象成一个便签应用,显示多个小型便签。我使用Linux,Windows不是我的目标平台。我也查看了gotk,但它似乎尚未提供WebKit.Webview。此外,我喜欢Webview能够调用JavaScript函数,并且通过Bind绑定Go函数,以便在JavaScript中使用。

我可以通过运行同一个Go程序的多个实例来实现我想要的功能,即每个Webview对应一个实例。我甚至可以在中间设置一个简单的“服务器”(同样是一个小型Go程序),用于启动这些Webview、作为持久化层、提供静态资源,甚至可能充当某种消息总线。这看起来很有趣(虽然会是一个有趣的项目),但我更希望只有一个Go应用程序,并为每个Webview设置专用通道。

因此,我想我可以尝试从同一个Go程序中启动两个Webview实例。我的第一个想法是,也许可以通过简单地使用goroutine来实现,就像这样:

// 代码无法工作
package main

import (
	"fmt"
	"github.com/webview/webview"
)

func runWebview(url string) {
	w := webview.New(true)
	defer w.Destroy()
	w.SetTitle("Minimal webview example")
	w.SetSize(800, 600, webview.HintNone)
	w.Navigate(url)
	w.Run()
}

func main() {
	go runWebview("https://en.m.wikipedia.org/wiki/Main_Page")
	go runWebview("https://www.reddit.com")
	var input string
	fmt.Scanln(&input)
}

然而,这无法工作,我收到了一个无效指针异常。我猜这是因为两个Webview都连接到同一个主程序。还有webview.NewWindow(debug bool, window unsafe.Pointer),可以传递一个指向GtkWindow的指针,但我不知道如何使用它。

直接使用Webkit.Webview是否更可行,也许可以扩展gotk(但我不知道如何做到这一点)?老实说,我很乐意抽象掉GTK。我知道还有Wails,但我只是简单看了一下。如果Wails可以启动多个Webview,我或许可以更仔细地研究一下。

有什么想法吗?我很乐意听听您对此的看法。


更多关于Golang中如何同时运行多个Webview实例的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

如果在 runWebview 的第一行调用 runtime.LockOSThread() 会怎么样?

更多关于Golang中如何同时运行多个Webview实例的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢您的建议。遗憾的是,这并没有带来任何改变。顺便提一下,如果我仅以这种方式运行一个Webview(在goroutine中,无论是否使用OSLockThread),它确实能按预期工作。

哎呀,你说得对!我最近遇到了Vim无法再复制到系统剪贴板的问题,不是有意重复发布相同的错误信息。

我认为webview的问题追踪器是个不错的选择,谢谢你的建议。我原以为自己完全走错了方向,但现在看来问题似乎更复杂一些。

我最近遇到了一个问题,Vim 不再将内容复制到系统剪贴板。

set clipboard=unnamed

clipboard 选项包含 “unnamed” 字符串时,无名寄存器将与 * 寄存器相同。因此,你可以直接复制到选择区域并粘贴,而无需在命令前加上 *

这个 issue 似乎暗示了在它们自己的线程上运行多个 webview 是受支持的。当你尝试使用多个 web 视图时,你得到的那个 nil 指针错误到底是什么?也许堆栈跟踪(如果有的话)能告诉我们一些信息。

如果不行,我下一步将尝试不使用 Go 来测试,看看问题是出在 Go 代码中还是 C 代码中。

trisub:

嗯,实际上即使重新运行同一个二进制文件,我也会得到不同的错误信息。比如像这样的内容:

realloc(): invalid old size
SIGABRT: abort
PC=0x7ffb0ffafd22 m=3 sigcode=18446744073709551610

或者

realloc(): invalid old size
SIGABRT: abort
PC=0x7ffb0ffafd22 m=3 sigcode=18446744073709551610

这些是相同的错误。我不确定问题是什么,因为我找不到实际的 webview.dll 的源代码。也许你可以在这里开一个 issue:Issues · webview/webview · GitHub

在Go中同时运行多个Webview实例确实需要特殊处理,因为webview库通常依赖主线程进行GUI操作。以下是两种可行的解决方案:

方案1:使用goroutine配合runtime.LockOSThread()

package main

import (
	"runtime"
	"github.com/webview/webview"
)

func runWebview(title, url string, done chan bool) {
	runtime.LockOSThread() // 关键:锁定到当前线程
	defer runtime.UnlockOSThread()
	
	w := webview.New(true)
	defer w.Destroy()
	w.SetTitle(title)
	w.SetSize(400, 300, webview.HintNone)
	w.Navigate(url)
	w.Run()
	
	done <- true
}

func main() {
	done1 := make(chan bool)
	done2 := make(chan bool)
	
	go runWebview("Wikipedia", "https://en.m.wikipedia.org", done1)
	go runWebview("Reddit", "https://www.reddit.com", done2)
	
	// 等待两个webview关闭
	<-done1
	<-done2
}

方案2:使用webview的窗口句柄(Linux GTK版本)

package main

/*
#cgo pkg-config: gtk+-3.0 webkit2gtk-4.0
#include <gtk/gtk.h>
#include <webkit2/webkit2.h>

static GtkWindow* create_gtk_window() {
    gtk_init(NULL, NULL);
    GtkWindow* window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
    return window;
}
*/
import "C"
import (
	"unsafe"
	"github.com/webview/webview"
)

func createWebview(title, url string) {
	// 创建GTK窗口
	gtkWindow := C.create_gtk_window()
	
	// 创建webview,传入GTK窗口指针
	w := webview.NewWindow(true, unsafe.Pointer(gtkWindow))
	defer w.Destroy()
	
	w.SetTitle(title)
	w.SetSize(400, 300, webview.HintNone)
	w.Navigate(url)
	w.Run()
}

func main() {
	// 需要在不同的goroutine中运行,每个都有独立的GTK上下文
	go createWebview("Note 1", "data:text/html,<h1>Note 1</h1>")
	go createWebview("Note 2", "data:text/html,<h1>Note 2</h1>")
	
	// 保持主程序运行
	select {}
}

方案3:使用Wails框架(推荐用于生产环境)

Wails内置了多窗口支持:

package main

import (
	"context"
	"github.com/wailsapp/wails/v2/pkg/application"
	"github.com/wailsapp/wails/v2/pkg/options"
	"github.com/wailsapp/wails/v2/pkg/options/assetserver"
)

func main() {
	app := application.NewWithOptions(&options.App{
		Title:  "Multi-Webview App",
		Width:  1024,
		Height: 768,
		AssetServer: &assetserver.Options{
			Assets: assetserver.Assets{
				FS: myAssets, // 你的静态资源
			},
		},
	})
	
	// 创建第一个窗口
	window1 := app.NewWebviewWindowWithOptions(&options.WebviewWindow{
		Title: "Note 1",
		Width: 400,
		Height: 300,
	})
	window1.LoadURL("data:text/html,<h1>Note 1 Content</h1>")
	
	// 创建第二个窗口
	window2 := app.NewWebviewWindowWithOptions(&options.WebviewWindow{
		Title: "Note 2",
		Width: 400,
		Height: 300,
		X:     450, // 设置位置
	})
	window2.LoadURL("data:text/html,<h1>Note 2 Content</h1>")
	
	// 运行应用
	err := app.Run(context.Background())
	if err != nil {
		println("Error:", err.Error())
	}
}

方案4:使用系统调用启动独立进程

package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

func launchWebview(url, title string) {
	// 编译时包含-ldflags="-H windowsgui"可隐藏控制台窗口
	exe, _ := os.Executable()
	cmd := exec.Command(exe, "--webview", url, "--title", title)
	cmd.Start()
}

func main() {
	if len(os.Args) > 1 && os.Args[1] == "--webview" {
		// 子进程模式:运行单个webview
		runSingleWebview(os.Args[3], os.Args[5])
		return
	}
	
	// 主进程:启动多个webview
	launchWebview("data:text/html,<h1>Note A</h1>", "Note A")
	launchWebview("data:text/html,<h1>Note B</h1>", "Note B")
	launchWebview("data:text/html,<h1>Note C</h1>", "Note C")
	
	fmt.Println("All webviews launched. Press Enter to exit.")
	fmt.Scanln()
}

func runSingleWebview(url, title string) {
	w := webview.New(true)
	defer w.Destroy()
	w.SetTitle(title)
	w.SetSize(400, 300, webview.HintNone)
	w.Navigate(url)
	w.Run()
}

绑定Go函数到JavaScript的示例

func runWebviewWithBind(title, url string) {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()
	
	w := webview.New(true)
	defer w.Destroy()
	
	// 绑定Go函数到JavaScript
	w.Bind("saveNote", func(content string) {
		fmt.Printf("Saving note: %s\n", content)
		// 这里可以添加保存逻辑
	})
	
	w.Bind("getNotes", func() []string {
		return []string{"Note 1", "Note 2", "Note 3"}
	})
	
	w.SetTitle(title)
	w.SetSize(400, 300, webview.HintNone)
	
	// 使用内联HTML包含JavaScript
	html := fmt.Sprintf(`
		<html>
		<body>
			<h1>%s</h1>
			<textarea id="content" rows="10" cols="40"></textarea>
			<button onclick="save()">Save</button>
			<script>
				function save() {
					const content = document.getElementById('content').value;
					window.saveNote(content);
				}
				
				// 调用Go函数获取数据
				const notes = window.getNotes();
				console.log('Notes from Go:', notes);
			</script>
		</body>
		</html>
	`, title)
	
	w.Navigate("data:text/html," + html)
	w.Run()
}

这些方案中,方案1和方案2适合简单用例,方案3(Wails)适合复杂的多窗口应用,方案4提供了进程级别的隔离。选择取决于具体需求和复杂性要求。

回到顶部