前言
现在常用的方案
- Duilib+CEF 只支持Windows的选择,优点是打包文件小(使用C++) QQ、微信、有道精品课。
- Qt+CEF 支持跨平台,缺点是打包文件大(使用C++)。
- WPF/(WPF+CEFSharp) 打包文件小,但是性能相比前两者弱,但比Electron强,内存占用高,只支持Windows。
- Electron 打包文件大,但是性能弱,内存占用高,支持跨平台。
几种方案都各有利弊,可以根据团队的情况选用,都是相对不错的,其他的方案比如Flutter,Java就不太推荐。
目前因为C++的技术栈的原因,我们的团队主要用WPF或者是Electron来做桌面端的开发。
有些界面用web开发会更好一点,所以这里就来集成CEFSharp来加载
注意
添加CEF会大幅增加安装包大小。
为什么使用CEF
- .NET 自带的 WebBrowser 是WEB 开发人员最讨厌的 IE,性能低下而且兼容性差
- Webkit: 项目已经不再支持
- Cef 是 Chrome 内核,性能和兼容性杠杠的。缺点就是带的 DLL 太多太大,一个发布版应该在150M左右,X86+X64一块就得快300M了。另外EXE加载速度也会稍慢。
安装依赖
通过Nuget安装,右击项目 -> 管理Nuget程序包 -> 在打开的界面中搜索CefSharp,依次安装 CefSharp.Common
和 CefSharp.Wpf
,至于 cef.redist.x64
和 cef.redist.x86
会自动安装。

配置解决方案平台
因为CefSharp不支持Any CPU
所以要配置x86、x64,点击菜单 生成
-> 配置管理器
。
选择解决方案平台,点击编辑,先将x64和x86删掉,再重新新建,重新配置比较容易些。
Any CPU的支持
如果我们要支持Any CPU
就要自己实现了。
using System.Windows; using System; using System.Runtime.CompilerServices; using CefSharp; using System.IO; using System.Reflection; using System.Windows.Threading; using CefSharpWpfDemo.Log; namespace CEFSharpTest { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { public App() { // Add Custom assembly resolver AppDomain.CurrentDomain.AssemblyResolve += Resolver; //Any CefSharp references have to be in another method with NonInlining // attribute so the assembly rolver has time to do it's thing. InitializeCefSharp(); } [MethodImpl(MethodImplOptions.NoInlining)] private static void InitializeCefSharp() { var settings = new CefSettings(); // Set BrowserSubProcessPath based on app bitness at runtime settings.BrowserSubprocessPath = Path.Combine( AppDomain.CurrentDomain.SetupInformation.ApplicationBase, Environment.Is64BitProcess ? "x64" : "x86", "CefSharp.BrowserSubprocess.exe" ); // Make sure you set performDependencyCheck false Cef.Initialize(settings, performDependencyCheck: false, browserProcessHandler: null); } // Will attempt to load missing assembly from either x86 or x64 subdir // Required by CefSharp to load the unmanaged dependencies when running using AnyCPU private static Assembly Resolver(object sender, ResolveEventArgs args) { if (args.Name.StartsWith("CefSharp")) { string assemblyName = args.Name.Split(new[] { ',' }, 2)[0] + ".dll"; string archSpecificPath = Path.Combine( AppDomain.CurrentDomain.SetupInformation.ApplicationBase, Environment.Is64BitProcess ? "x64" : "x86", assemblyName ); return File.Exists(archSpecificPath) ? Assembly.LoadFile(archSpecificPath) : null; } return null; } } }
使用
使用时可以直接在xaml文件中直接添加ChromiumWebBrowser控件,不过ChromiumWebBrowser控件特别消耗内存,所以代码里动态添加也是一种不错的选择。
在xaml中添加浏览器
xmal文件头部插入引用
xmlns:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
添加控件如下:
<Grid x:Name="ctrlBrowerGrid"> <wpf:ChromiumWebBrowser x:Name="Browser"/> </Grid>
cs文件中操作控件访问网址:
Browser.Load("https://www.psvmc.cn");
代码添加浏览器
添加浏览器类:
using CefSharp.Wpf; using System.ComponentModel; using System.Windows; namespace CEFSharpTest.view { internal sealed class CollapsableChromiumWebBrowser : ChromiumWebBrowser { public CollapsableChromiumWebBrowser() { Loaded += BrowserLoaded; } private void BrowserLoaded(object sender, RoutedEventArgs e) { // Avoid loading CEF in designer if (DesignerProperties.GetIsInDesignMode(this)) { return; } // Avoid NRE in AbstractRenderHandler.OnPaint ApplyTemplate(); } } }
动态添加和操作控件:
private CollapsableChromiumWebBrowser MyBrowser = null; private void InitWebBrower() { MyBrowser = new CollapsableChromiumWebBrowser(); //页面插入控件 ctrlBrowerGrid.Children.Add(MyBrowser); //这里不能用Load()的方法,会报错。 MyBrowser.Address = "https://www.psvmc.cn"; }
获取Cookie和Html
添加Cookie访问类
using CefSharp;
using System; namespace CEFSharpTest.view { public class CookieVisitor : ICookieVisitor { private string Cookies = null; public event Action<object> Action; public bool Visit(Cookie cookie, int count, int total, ref bool deleteCookie) { if (count == 0) Cookies = null; Cookies += cookie.Name + "=" + cookie.Value + ";"; deleteCookie = false; return true; } public void Dispose() { if (Action != null) Action(Cookies); return; } } }
浏览器控件访问网址,并设置回调
private CollapsableChromiumWebBrowser MyBrowser = null; private void InitWebBrower() { MyBrowser = new CollapsableChromiumWebBrowser(); //页面插入控件 ctrlBrowerGrid.Children.Add(MyBrowser); MyBrowser.FrameLoadEnd += Browser_FrameLoadEnd; //这里不能用Load()的方法,会报错。 MyBrowser.Address = "https://www.psvmc.cn"; } private async void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e) { CookieVisitor visitor = new CookieVisitor(); string html = await MyBrowser.GetSourceAsync(); Console.WriteLine("html:" + html); visitor.Action += RecieveCookie; Cef.GetGlobalCookieManager().VisitAllCookies(visitor); return; } public async void RecieveCookie(object data) { string cookies = (string)data; Console.WriteLine("cookies:" + cookies); return; }
加载本地页面和JS回调
添加HTML
项目下添加html路径html\index.html
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8" /> <script type="text/javascript"> function callback() { callbackObj.showMessage('message from js'); } function alert_msg(msg) { alert(msg); } </script> </head> <body> <button onclick="callback()">Click</button> <style> * { margin: 0; padding: 0; } body { background-color: #f3f3f3; width: 100vw; height: 100vh; display:flex; align-items:center; justify-content:center; } </style> </body> </html>
复制页面到目标目录
方式1
项目
->属性
->生成事件
->生成前事件命令行
添加如下
xcopy /Y /i /e $(ProjectDir)\html $(TargetDir)\html
方式2
文件右键点击属性,设置复制到输出目录和生成操作。
如果文件较多建议用方式1 。

代码
注册一个JS对象
private ChromiumWebBrowser MyBrowser = null; private void InitWebBrower() { CefSettings cSettings = new CefSettings() { Locale = "zh-CN", CachePath = Directory.GetCurrentDirectory() + @"\Cache" }; cSettings.MultiThreadedMessageLoop = true; cSettings.CefCommandLineArgs.Add("proxy-auto-detect", "0"); cSettings.CefCommandLineArgs.Add("--disable-web-security", ""); //Disable GPU acceleration cSettings.CefCommandLineArgs.Add("disable-gpu"); //Disable GPU vsync cSettings.CefCommandLineArgs.Add("disable-gpu-vsync"); //此配置可以允许摄像头打开摄像 cSettings.CefCommandLineArgs.Add("enable-media-stream", "1"); Cef.Initialize(cSettings); string pagepath = string.Format(@"{0}html\index.html", AppDomain.CurrentDomain.BaseDirectory); if (!File.Exists(pagepath)) { MessageBox.Show("HTML不存在: " + pagepath); return; } // Create a browser component MyBrowser = new ChromiumWebBrowser(); //禁用右键菜单 MyBrowser.MenuHandler = new MenuHandler(); //禁用弹窗 MyBrowser.LifeSpanHandler = new LifeSpanHandler(); MyBrowser.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#f3f3f3")); //页面插入控件 ctrlBrowerGrid.Children.Add(MyBrowser); MyBrowser.Address = pagepath; MyBrowser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true; MyBrowser.JavascriptObjectRepository.Register( "callbackObj", new CallbackObjectForJs(), isAsync: true, options: BindingOptions.DefaultBinder ); }
调用JS方法
private void Button_Click(object sender, RoutedEventArgs e) { MyBrowser.ExecuteScriptAsync("alert_msg('123')"); }
事件回调类
public class CallbackObjectForJs { public void showMessage(string msg) { MessageBox.Show(msg); } }
禁用右键菜单的类
public class MenuHandler : IContextMenuHandler { public void OnBeforeContextMenu( IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model ) { model.Clear(); } public bool OnContextMenuCommand( IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags ) { return false; } public void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame) { } public bool RunContextMenu( IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model, IRunContextMenuCallback callback ) { return false; } }
原窗口打开链接的类
public class LifeSpanHandler : ILifeSpanHandler { //弹出前触发的事件 public bool OnBeforePopup( IWebBrowser webBrowser, IBrowser browser, IFrame frame, string targetUrl, string targetFrameName, WindowOpenDisposition targetDisposition, bool userGesture, IPopupFeatures popupFeatures, IWindowInfo windowInfo, IBrowserSettings browserSettings, ref bool noJavascriptAccess, out IWebBrowser newBrowser) { //使用源窗口打开链接,取消创建新窗口 newBrowser = null; var chromiumWebBrowser = (ChromiumWebBrowser)webBrowser; chromiumWebBrowser.Load(targetUrl); return true; } public void OnAfterCreated(IWebBrowser chromiumWebBrowser, IBrowser browser) { } public bool DoClose(IWebBrowser chromiumWebBrowser, IBrowser browser) { return true; } public void OnBeforeClose(IWebBrowser chromiumWebBrowser, IBrowser browser) { } }
注意项
API变更
//Old Method
MyBrowser.RegisterAsyncJsObject("callbackObj", new CallbackObjectForJs(), options: BindingOptions.DefaultBinder); //Replaced with MyBrowser.JavascriptObjectRepository.Settings.LegacyBindingEnabled = true; MyBrowser.JavascriptObjectRepository.Register("callbackObj", new CallbackObjectForJs(), isAsync: true, options: BindingOptions.DefaultBinder);
本地文件路径
文件路径中不能包含特殊字符,否则不能加载,之前我的项目在
C#
目录下,就一直加载不了页面。