前言
現在常用的方案
- 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#
目錄下,就一直加載不了頁面。