解决CEFSharp关闭时主程序退出的问题
昨天在写代码时,碰到了一个问题。
Form里面有一个TabControl,里面有多个TabPage,每个TabPage里面都放了一个CefSharp.WinForms.ChromiumWebBrowser用来展示网页。但是无论关闭任何一个Tabpage,主程序都会退出,并且在输出页面报 “程序[2508] XXX.vshost.exe 已退出,返回值为 -1073741819 (0xc0000005) ‘Access violation’。”
从这个报错上来看,好像是访问受限,也就是进程在访问已被释放的资源。
于是着手解决资源释放问题,试了好多种办法都不奏效。只要一关闭任何一个TabPage,主程序都会自动关闭并且抛异常。
最坑爹的是,官方示例里面的“CefSharp.WinForms.Example”也是错误的,也会在关闭TabPage的时候释放资源,抛异常。
最终,皇天不负有心人,终于还是找到了解决途径。
在程序初始化的时候,我们需要初始化浏览器参数,以前我都是这样初始化的:
CefSharp.CefSettings setting = new CefSharp.CefSettings();
CefSharp.Cef.Initialize(setting);
但是其实应该这样初始化:
CefSharp.CefSettings setting = new CefSharp.CefSettings();
CefSharp.Cef.Initialize(setting, false, false);
看接口注释:
//
// 摘要:
// Initializes CefSharp with user-provided settings. This function should be
// called on the main application thread to initialize the CEF browser process.
//
// 参数:
// cefSettings:
// CefSharp configuration settings.
//
// shutdownOnProcessExit:
// When the Current AppDomain (relative to the thread called on) Exits(ProcessExit
// event) then Shudown will be called.
//
// performDependencyCheck:
// Check that all relevant dependencies avaliable, throws exception if any are
// missing
public static bool Initialize(CefSettings cefSettings, bool shutdownOnProcessExit, bool performDependencyCheck);
第二个参数就是用来设置是否在退出的时候释放资源,默认为true,就会在当前页面退出的时候调用Shudown。然后Shudown就会把整个浏览器的资源释放掉。当浏览器资源被释放了以后,其他页面访问资源就变成了空指针访问,然后就会挂掉。
所以我们如果是做多页面窗口的话,需要手动将这个参数置为false。这样才能保证一个页面关闭时,其他页面不受影响。
至于当前被关闭页面的资源释放,我们需要显示的调用ChromiumWebBrowser的父控件的Dispose方法。如下官方示例代码所示:
private void CloseTabToolStripMenuItemClick(object sender, EventArgs e)
{
var currentIndex = browserTabControl.SelectedIndex;
var tabPage = browserTabControl.Controls[currentIndex];
var control = GetCurrentTabControl();
if (control != null)
{
control.Dispose();
}
browserTabControl.Controls.Remove(tabPage);
tabPage.Dispose();
}
最后,在主程序退出时,需要显式的调用CefSharp.Cef.Shutdown(),用来释放资源;比如FormClosing的时候。
另外,CefSharp.Cef.Initialize需要在和ChromiumWebBrowser使用的地方放在一起,不可跨DLL初始化。因为静态函数,静态变量是无法跨DLL使用的。所以如果初始化和使用的地方不在同一个DLL内,则无效。