https://www.toutiao.com/article/7275130681343197711/
为 WPF 或 WinForms 应用程序选择浏览器组件,对于那些搜索基于Chrome的解决方案的人来说, DotNetBrowser[1]和CefSharp[2]是最明显的选择。
本文是我们的客户在考虑其项目的开源库和商业库时提出的最常见比较点的汇编。
引擎
CefSharp 实际上是 Chromium Embedded Framework[3] (CEF) 的 .NET 包装器。包装通过 C++/CLI 完成。
DotNetBrowser 在底层不使用 CEF 或 C++/CLI。相反,它采用了自己的方法直接与 Chromium 集成。它启动一个功能齐全的 Chromium 引擎,并通过进程间通信 (IPC) 与其进行通信。
架构
在 CefSharp 中,Chromium 引擎直接在您的 .NET 进程中初始化[4]。初始化和关闭都必须在主应用程序线程(通常是 UI 线程)中执行。在不同的线程中调用它们通常会导致冻结。
此外,每个进程可以执行一次初始化和关闭。这个限制来自 CEF 本身。在执行关闭后尝试重新初始化 CefSharp 将导致错误。
CefSharp architecture
在 DotNetBrowser 中,Chromium 引擎在单独的本机进程中进行初始化。不需要在主 UI 线程上执行此操作——即使在工作线程中也可以执行此操作。
您可以同时初始化和使用具有不同配置的多个 Chromium 引擎,这在 CefSharp 中是不可能的。您可以在不再需要 Chromium 时将其关闭并随时重新初始化。
DotNetBrowser architecture
稳定性和内存使用
在单独的进程中运行 Chromium 有更多优点:
在这种情况下,内存消耗要低得多,这对于 32 位应用程序来说似乎很关键。
在 CefSharp 中,如果 CEF 或 C++/CLI 绑定内部出现问题,这将导致整个 .NET 应用程序崩溃而无法处理这种情况。这就不太妙了,因为 .NET 应用程序可能会丢失或损坏用户的数据。
对于 DotNetBrowser,Chromium 内部的错误不会导致 .NET 应用程序崩溃。此外,甚至可以在托管代码中正确检测和处理这一切。例如,如果发生这种情况,那么您可以重新初始化 Chromium 并恢复用户会话。
应用程序域
由于其架构,CefSharp 不能在非默认 AppDomain 中使用[5]。因此,它不能用于通过 VSTO 插件或 Excel-DNA 将 Chromium 嵌入到 Office 应用程序中[6]。Office VSTO 将加载项加载到单独的 AppDomain 中以进行隔离。DotNetBrowser 在非默认 AppDomain 中运行。事实上,可以在不同的 AppDomain 中创建多个 Chromium 引擎并同时使用它们。因此,DotNetBrowser 可用于创建 VSTO 加载项。
AnyCPU
在针对 AnyCPU 的应用程序中使用 CefSharp 时,您会发现它在这些应用程序的 64 位环境中无法正常工作。
这儿有几个选项[7]可以解决这个问题。其中之一是让您的应用程序始终在 32 位模式下运行,另一个更复杂,需要修改项目文件(.csproj 或 .vbproj)和代码。
在 DotNetBrowser 中,AnyCPU 支持开箱即用。因此,不需要类似的调整。
H.264, AAC
视频和音频通常使用专有编解码器进行编码,例如 H.264 和 AAC。此媒体无法在 CefSharp 中播放。
要在 CefSharp 中启用这些编解码器,您需要在启用专有编解码器的情况下自行重建 CEF。这是一项相当复杂的任务,可能需要长达一个月的时间[8]。
在 DotNetBrowser 中默认禁用专有编解码器。可以通过编程方式启用它们,而无需重建库:
InitializeCodecs.cs
IEngine engine = EngineFactory.Create(new EngineOptions.Builder
{
ProprietaryFeatures = ProprietaryFeatures.H264 | ProprietaryFeatures.Aac
}.Build());
安全
Chromium 通过利用操作系统为它们提供的安全性来限制其渲染器和实用程序进程。此功能称为 Chromium沙箱[9]。其主要目的是防止第三方代码对计算机进行持久更改或访问机密信息。
CefSharp 不支持 Chromium 沙箱[10]。这个限制来自 CEF 本身。
DotNetBrowser 支持沙箱并默认启用。如有必要,可以在初始化期间将其禁用[11]。
CefSharp 在 .NET 进程中启动 Chromium。这使您的应用程序容易受到 CEF 和 Chromium 中的漏洞的影响。如果恶意软件获得了对 Chromium 内存的访问权,它也会获得对 .NET 内存的访问权。
DotNetBrowser 在单独的进程中启动 Chromium。
Chromium 漏洞保留在 Chromium 中。
Visual Studio设计器
现代 WPF 和 Windows 窗体应用程序通常是在设计器的帮助下在 Visual Studio 中创建的。这种方法总体上简化了 UI 创建并节省了大量时间和精力。
CefSharp 提供有限的设计器支持[12]。如果应用程序本身以 x86 为目标,则其控件将在设计器中正确处理。AnyCPU 可能会工作,但尚未经过彻底测试。
DotNetBrowser 控件是纯 UI 控件,它们在代码中显式初始化。您可以在设计器中不受任何限制地使用它们。安装 NuGet 包或 VSIX 扩展后,BrowserView 控件出现在工具箱中。它可以像任何其他常规 UI 控件一样被拖到窗体或窗口上。
嵌入应用程序 UI
CefSharp 提供 WPF 和 Windows 窗体支持。但是,它的 WPF 实现只能在离屏渲染模式[13]下工作。此实现具有有限的触摸屏和 IME[14] 支持。
DotNetBrowser 在两种渲染模式下同时支持 WPF 和 Windows 窗体。在硬件加速模式下,触摸、手势和 IME 由 Chromium 自行处理,因此它们开箱即用。在离屏模式下,存在一些已知的限制[15]。
以下是将 CefSharp 嵌入 WPF 窗口的方法:
<Window x:Class="CefSharpWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpf="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
Title="MainWindow" Height="450" Width="800">
<Grid>
<wpf:ChromiumWebBrowser Address="https://www.google.com"/>
</Grid>
</Window>
就是这样,在最简单的情况下,不再需要编写代码。但是,在这种情况下,CefSharp 初始化和关闭是隐式执行的,很难确定它是否已经在某个点初始化。
将 DotNetBrowser 嵌入 WPF 窗口的过程需要额外的步骤。
例如:
MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WPF="clr-namespace:DotNetBrowser.Wpf;assembly=DotNetBrowser.Wpf"
x:Class="Embedding.Wpf.MainWindow"
Title="MainWindow" Height="480" Width="800" Closed="Window_Closed">
<Grid>
<WPF:BrowserView Name="browserView" />
</Grid>
</Window>
MainWindow.xaml.cs
publicpartialclassMainWindow : Window
{
privateconststring Url = "https://www.google.com";
privatereadonly IBrowser browser;
privatereadonly IEngine engine;
public MainWindow()
{
// Create and initialize the IEngine instance.
EngineOptions engineOptions = new EngineOptions.Builder
{
RenderingMode = RenderingMode.HardwareAccelerated
}.Build();
engine = EngineFactory.Create(engineOptions);
// Create the IBrowser instance.
browser = engine.CreateBrowser();
InitializeComponent();
// Initialize the WPF BrowserView control.
browserView.InitializeFrom(browser);
browser.Navigation.LoadUrl(Url);
}
private void Window_Closed(object sender, EventArgs e)
{
browser?.Dispose();
engine?.Dispose();
}
}
在这里,大部分代码都与 Chromium 实例和 IBrowser 实例的显式初始化和关闭有关。UI 控件初始化是通过调用 InitializeFrom() 显式执行的。这种方法可以更好地控制初始化和关闭过程,并且更容易自定义初始 Chromium 配置。
高DPI
在 CefSharp 中,浏览器子进程的 默认 DPI 感知[16] 是 Per-Monitor。因此,桌面应用程序应具备 DPI 感知功能,才能在高 DPI 显示器(DPI 比例设置大于 100% 的显示器)上正确运行。在其他情况下,浏览器内容可能无法正确呈现,例如:
DotNetBrowser 以不同的方式支持高 DPI。在初始化过程中,它会检查当前进程的 DPI 感知,并为相应的 Chromium 引擎设置匹配的 DPI 感知。因此,无需让您的应用程序显式识别 DPI 以避免在高 DPI 显示上呈现伪影。
Headless
DotNetBrowser 和 CefSharp 都可以在没有 UI 的应用程序中使用。
在 CefSharp 中,
CefSharp.OffScreen.ChromiumWebBrowser 用于此目的。初始化过程通常保持不变。但是,如果您的代码使用 async/await 模式,则需要使用同步上下文来确保在主线程上而不是在不同的工作线程上执行初始化和关闭。
要在没有 UI 的应用程序中使用 DotNetBrowser,您需要像往常一样执行初始化。在这种情况下,没有需要初始化的 BrowserView。即使您的代码使用async/await模式,也无需创建和使用同步上下文。
API和功能
这两种产品都有许多可用的功能。在本文中,我将比较几个最重要的,以展示 API 的不同之处。
DOM访问
在 CefSharp 中,您只能通过执行 JavaScript 调用来访问 DOM。
例如:
CefSharpDom.cs
var script = @”
document.getElementsByName(‘question’)[0].value = ‘CefSharp Example’;
document.getElementsByName(‘btn’)[0].click();
“;
browser.ExecuteScriptAsync(script);
DotNetBrowser 提供了丰富的 DOM API,可用于直接从 .NET 执行以下操作:
访问和修改 DOM 树;
更改 HTML 元素属性;
订阅 DOM 事件并从 .NET 代码中调度它们。
例如,以下是如何在 DotNetBrowser 中的网页上执行相同的操作:
DotNetBrowserDom.cs
IDocument document = browser.MainFrame.Document;
(document.GetElementByName(“question”) as IInputElement).Value = “DotNetBrowser Example”;
document.GetElementByName(“btn”).Click();
因此,在 DotNetBrowser 中与网页执行复杂的交互要方便得多。无需编写难以调试和支持的复杂 JavaScript 代码。DotNetBrowser 中的 DOM API 不是一组 JavaScript 调用的包装器。它直接对 Blink 引擎进行 IPC 调用。
与JavaScript交互
执行JavaScript并处理结果
CefSharp 和 DotNetBrowser 都提供了在网页上执行 JavaScript 的能力。
在 CefSharp 中,有两种方法可用于此目的,ExecuteJavaScriptAsync 和 EvaluateScriptAsync。两者都可用于浏览器本身(通过扩展方法)或其中的一个框架:
CefSharpExecuteJs.cs
// Execute JavaScript without returning a result. The method returns
// before the script has actually been executed.
browser.ExecuteJavaScriptAsync(“alert(‘All Resources Have Loaded’);”);
// Evaluate some Javascript code. The script will be executed asynchronously
// and the method returns a Task encapsulating the response from the
// JavaScript.
JavascriptResponse response = await browser.EvaluateScriptAsync(script);
然后使用 JavascriptResponse.Result 获取执行结果。
可能的结果类型有 bool, int, long, double, string, List