go version go1.20 windows/amd64
先要了解一些第三方库
1、webview/webview
它是一个跨平台
的轻量级的webview库,面向的是C/C++,使用它可以构建跨平台的GUI。webview就是浏览器内核,在不同操作系统上是不同的库,比如在windows上为webview2,所以webview与webview2不要搞混了。从功能上,它实现了Javascript与C/C++之间的相互调用,即bindings。具体实现原理应该与wails差不多。
在不同平台上的支持如下
Platform | Technologies |
---|---|
Linux | GTK, WebKitGTK |
macOS | Cocoa, WebKit |
Windows | Windows API, WebView2 |
windows api 是windows系统提供的DLL(user32.dll),以此来构建windows上原生的GUI程序。
webview2是微软的Edge浏览器内核,用它来渲染前端页面,下载地址,所以这是两种不同的实现方式。而且webview2只支持windows系统。如果你的系统是Windows 10+,那么 WebView2 Runtime会默认安装了。
2、webview/webview_go
它使用Golang将webview/webview包装了一下,但是使用的是CGO,即webview/webview_go依赖于webview/webview,并使用CGO将依赖引入。
项目还不完善,我的本地系统x86_64-w64-mingw32
下面找不到EventToken.h
头文件,所以代码无法运行,Support for EventToken.h on mingw64 #45 依然没有解决。
3、jchv/go-webview2
它借鉴了webview/webview,使用Golang包装了webview2,不需要使用CGO,这是与webview_go的不同,当然此包只支持windows,因为它只是包装了webview2。在wails中使用的就是 jchv/go-webview2
它提供了一个示例:cmd/demo/main.go
import ("log""github.com/jchv/go-webview2"
)func main() {w := webview2.NewWithOptions(webview2.WebViewOptions{Debug: true,AutoFocus: true,WindowOptions: webview2.WindowOptions{Title: "Minimal webview example",Width: 800,Height: 600,IconId: 2, // icon resource idCenter: true,},})if w == nil {log.Fatalln("Failed to load webview.")}defer w.Destroy()w.SetSize(800, 600, webview2.HintFixed)w.Navigate("https://en.m.wikipedia.org/wiki/Main_Page")w.Run()
}
go-webview2简要说明
webviewloader\module.go
先使用windows.NewLazyDLL("WebView2Loader")
加载WebView2Loader.dll
,如果没有找到,它会使用go-winloader库来加载go-webview2自带的WebView2Loader.dll
文件(webviewloader/sdk/x64下)。
webview2的参考文档:https://learn.microsoft.com/zh-cn/microsoft-edge/webview2/
go-webview2基本已经封装的很好了,下面是一个WebView对象提供的功能。
// WebView is the interface for the webview.
type WebView interface {// Run runs the main loop until it's terminated. After this function exits -// you must destroy the webview.Run()// Terminate stops the main loop. It is safe to call this function from// a background thread.Terminate()// Dispatch posts a function to be executed on the main thread. You normally// do not need to call this function, unless you want to tweak the native// window.Dispatch(f func())// Destroy destroys a webview and closes the native window.Destroy()// Window returns a native window handle pointer. When using GTK backend the// pointer is GtkWindow pointer, when using Cocoa backend the pointer is// NSWindow pointer, when using Win32 backend the pointer is HWND pointer.Window() unsafe.Pointer// SetTitle updates the title of the native window. Must be called from the UI// thread.SetTitle(title string)// SetSize updates native window size. See Hint constants.SetSize(w int, h int, hint Hint)// Navigate navigates webview to the given URL. URL may be a data URI, i.e.// "data:text/text,<html>...</html>". It is often ok not to url-encode it// properly, webview will re-encode it for you.Navigate(url string)// SetHtml sets the webview HTML directly.// The origin of the page is `about:blank`.SetHtml(html string)// Init injects JavaScript code at the initialization of the new page. Every// time the webview will open a the new page - this initialization code will// be executed. It is guaranteed that code is executed before window.onload.Init(js string)// Eval evaluates arbitrary JavaScript code. Evaluation happens asynchronously,// also the result of the expression is ignored. Use RPC bindings if you want// to receive notifications about the results of the evaluation.Eval(js string)// Bind binds a callback function so that it will appear under the given name// as a global JavaScript function. Internally it uses webview_init().// Callback receives a request string and a user-provided argument pointer.// Request string is a JSON array of all the arguments passed to the// JavaScript function.//// f must be a function// f must return either value and error or just errorBind(name string, f interface{}) error
}
Navigate方法的参数
- 网络地址:https://en.m.wikipedia.org/wiki/Main_Page
- 文件系统地址:file:///D:/dev/php/magook/trunk/server/go-webview/test.html,要使用绝对地址。当然,只要是浏览器能识别的文件都行,比如图片,网页,TXT文件等等。
- 直接展示内容:就是将文件的内容复制进去,此时就需要指定内容是什么形式的,如果不指定就无法展示出来。格式为
mimetype,content
,其中 mimetype的常用的有data:image/gif, data:image/webp, data:image/jpeg, data:image/png, data:text/html, data:text/text
,它会严格按照mimetype渲染,所以类型一定要对。
Bind 方法,进行了绑定之后,js就可以调用name方法,而这个name方法在go中的实现逻辑就是 f。
func (w *webview) Bind(name string, f interface{}) error {v := reflect.ValueOf(f)if v.Kind() != reflect.Func {return errors.New("only functions can be bound")}if n := v.Type().NumOut(); n > 2 {return errors.New("function may only return a value or a value+error")}w.m.Lock()w.bindings[name] = fw.m.Unlock()w.Init("(function() { var name = " + jsString(name) + ";" + `var RPC = window._rpc = (window._rpc || {nextSeq: 1});window[name] = function() {var seq = RPC.nextSeq++;var promise = new Promise(function(resolve, reject) {RPC[seq] = {resolve: resolve,reject: reject,};});window.external.invoke(JSON.stringify({id: seq,method: name,params: Array.prototype.slice.call(arguments),}));return promise;}})()`)return nil
}
它这里使用的是window.external.invoke()
方法,即调用外部方法,因为它包装的是new Promise
,所以使用 Promise 的机制来处理返回值。
在RPC调用方面,其内部机制还是postMessage()消息事件,更具体的可以阅读我关于wails的文章。
如果 Debug = true,那么按F12可以打开调试。
我的代码如下:
package mainimport ("errors""log"webview2 "github.com/jchv/go-webview2"
)func main() {w := webview2.NewWithOptions(webview2.WebViewOptions{Debug: true,AutoFocus: true,WindowOptions: webview2.WindowOptions{Title: "Minimal webview example",Width: 800,Height: 600,IconId: 2, // icon resource idCenter: true,},})if w == nil {log.Fatalln("Failed to load webview.")}defer w.Destroy()w.Bind("test_love", Great)w.Navigate("file:///D:/dev/php/magook/trunk/server/go-webview/test.html")w.Run()
}func Great(param string) (result string, err error) {if param == "I love you" {return "I love you too", nil} else if param == "I hate you" {return "", errors.New("break up")} else {return "I don't know", nil}
}
特别注意:
Bind() 一定要在 Navigate() 之前调用,否则就是无效的!!
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>爱吗?</title>
</head><body><div class="box"><input type="button" value="爱你" class="btn" onclick="love()"><input type="button" value="不爱了" class="btn" onclick="nolove()"></div>
</body></html>
<style>.box {margin-top: 100px;text-align: center;}.btn {display: inline-block;width: 100px;height: 50px;border-radius: 10px;border: 2px solid green;text-align: center;}
</style>
<script>function love() {window["test_love"]("I love you").then(result => {alert(result);}).catch(err => {alert(err);});}function nolove() {window["test_love"]("I hate you").then(result => {alert(result);}).catch(err => {alert(err);});}
</script>
打开调试,console.log(window)
,或者 console.log(window.test_love)
,就能看到你定义的方法已经被注册到了window对象上。
运行效果:
由于wails引入了go-webview2,并做了扩展,所以直接使用wails框架即可。