昨天在写代码时,碰到了一个问题。

Form里面有一个TabControl,里面有多个TabPage,每个TabPage里面都放了一个CefSharp.WinForms.ChromiumWebBrowser用来展示网页。但是无论关闭任何一个Tabpage,主程序都会退出,并且在输出页面报 “程序[2508] XXX.vshost.exe 已退出,返回值为 -1073741819 (0xc0000005) ‘Access violation’。”

从这个报错上来看,好像是访问受限,也就是进程在访问已被释放的资源。

于是着手解决资源释放问题,试了好多种办法都不奏效。只要一关闭任何一个TabPage,主程序都会自动关闭并且抛异常。

最坑爹的是,官方示例里面的“CefSharp.WinForms.Example”也是错误的,也会在关闭TabPage的时候释放资源,抛异常。

最终,皇天不负有心人,终于还是找到了解决途径。

在程序初始化的时候,我们需要初始化浏览器参数,以前我都是这样初始化的:

1
2
CefSharp.CefSettings setting = new CefSharp.CefSettings();
CefSharp.Cef.Initialize(setting);

但是其实应该这样初始化:

1
2
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方法。如下官方示例代码所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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内,则无效。