使用 Shadow Copy 启动的 CefSharp 应用程序
CefSharp application launched with Shadow Copy
我创建了一个使用 ChromiumBrowser 的 Windows 表单应用程序。该应用程序由以下组件组成:
- 主要应用程序
- Web 浏览器库
- 启动器应用程序
当我正常启动我的应用程序时,网络浏览器工作正常。如果我从启动器启动我的应用程序,Web 浏览器将无法工作。它告诉我以下错误:
Unhandled exception of 'System.IO.FileNotFoundException' in Unknown module.
Cannot load file or assembly 'CefSharp, Version=57.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138' or a relative dependency.
Unable to found the specified file.
我不仅需要使用启动器进行更新,而且由于应用程序分布在网络上,有时访问服务器上的文件会出现问题。
问题不仅仅与我的应用有关。我在下面 post 创建了一个测试解决方案,但我遇到了同样的问题。
项目说明
- Cefsharp 运行时位于 C:\Program Files (x86)\CEFRuntime\x64 和 C:\Program Files (x86)\CEFRuntime\x86。 (我创建了一个安装程序来复制这个位置的运行时文件)。运行时基于 NuGet 包。
- 所有可执行文件都在 AnyCpu 中编译(AnyCpu 支持)
Cefsharp 版本 57(Cef redist 3.2987.1601)
运行时内容
x64 文件夹
- cef.pak
- CefSharp.BrowserSubprocess.Core.dll
- CefSharp.BrowserSubprocess.Core.pdb
- CefSharp.BrowserSubprocess.exe
- CefSharp.BrowserSubprocess.pdb
- CefSharp.Core.dll
- CefSharp.Core.pdb
- CefSharp.Core.xml
- CefSharp.dll
- CefSharp.pdb
- CefSharp.WinForms.dll
- CefSharp.WinForms.pdb
- CefSharp.WinForms.XML
- CefSharp.XML
- cef_100_percent.pak
- cef_200_percent.pak
- cef_extensions.pak
- chrome_elf.dll
- d3dcompiler_47.dll
- devtools_resources.pak
- icudtl.dat
- libcef.dll
- libEGL.dll
- libGLESv2.dll
- natives_blob.bin
- snapshot_blob.bin
- widevinecdmadapter.dll
- locales 文件夹(包含所有 .pak)
x86 文件夹
- cef.pak
- CefSharp.BrowserSubprocess.Core.dll
- CefSharp.BrowserSubprocess.Core.pdb
- CefSharp.BrowserSubprocess.exe
- CefSharp.BrowserSubprocess.pdb
- CefSharp.Core.dll
- CefSharp.Core.pdb
- CefSharp.Core.xml
- CefSharp.dll
- CefSharp.pdb
- CefSharp.WinForms.dll
- CefSharp.WinForms.pdb
- CefSharp.WinForms.XML
- CefSharp.XML
- cef_100_percent.pak
- cef_200_percent.pak
- cef_extensions.pak
- chrome_elf.dll
- d3dcompiler_47.dll
- devtools_resources.pak
- icudtl.dat
- libcef.dll
- libEGL.dll
- libGLESv2.dll
- natives_blob.bin
- snapshot_blob.bin
- widevinecdmadapter.dll
- locales 文件夹(包含所有 .pak)
我post给我同样错误的测试方案
测试解决方案
测试方案由三个项目组成:
- WhosebugIssueLauncher
- WhosebugIssue(参考 WebBrowser)
- WebBrowser(包含网络浏览器的 dll 库)
代码如下:
WhosebugIssueLauncher 项目
Program.cs
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace WhosebugIssueLauncher {
/// <summary>
/// Launcher program
/// </summary>
internal static class Program {
/// <summary>
/// Launcher body
/// </summary>
[STAThread, LoaderOptimization(LoaderOptimization.MultiDomainHost)]
private static void Main() {
//Initialize path of application
string startupPath = Environment.CurrentDirectory;
string cachePath = Path.Combine(Path.GetTempPath(), "Program-" + Guid.NewGuid());
string assemblyPath = CanonicalizePathCombine(startupPath, @"..\..\..\WhosebugIssue\bin\Debug\");
string executablePath = Path.Combine(assemblyPath, "WhosebugIssue.exe");
string configFile = executablePath + ".config";
//Start App Domain
try {
var setup = new AppDomainSetup() {
ApplicationName = "WhosebugIssue",
ShadowCopyFiles = "true",
ShadowCopyDirectories = assemblyPath,
CachePath = cachePath,
ConfigurationFile = configFile
};
var domain = AppDomain.CreateDomain("WhosebugIssue", AppDomain.CurrentDomain.Evidence, setup);
domain.ExecuteAssembly(executablePath);
AppDomain.Unload(domain);
}
catch (Exception ex) {
MessageBox.Show(ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
//Empty cache path
try {
Directory.Delete(cachePath, true);
}
catch (Exception) {
//DO NOTHING
}
}
private static string CanonicalizePathCombine(string sourcePath, string destPath) {
string resultPath = Path.Combine(sourcePath, destPath);
var sb = new StringBuilder(Math.Max(260, 2 * resultPath.Length));
PathCanonicalize(sb, resultPath);
return sb.ToString();
}
[DllImport("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool PathCanonicalize([Out] StringBuilder sb, string src);
}
}
Whosebug 问题项目
WebControlForm.cs
using System.Windows.Forms;
using WebBrowser;
namespace WhosebugIssue {
/// <summary>
/// Form that contains the webbrowser control
/// </summary>
public class WebControlForm : Form {
/// <summary>
/// Create a new web control form
/// </summary>
public WebControlForm() {
InitializeComponent();
Controls.Add(new CefControl { Dock = DockStyle.Fill });
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.SuspendLayout();
//
// WebControlForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(529, 261);
this.Name = "WebControlForm";
this.Text = "WebControlForm";
this.ResumeLayout(false);
}
#endregion
}
}
MainForm.cs
using System;
using System.Windows.Forms;
namespace WhosebugIssue {
/// <summary>
/// Main application form
/// </summary>
public partial class MainForm : Form {
/// <summary>
/// Creates the main form
/// </summary>
public MainForm() {
InitializeComponent();
}
/// <summary>
/// Show a new Web Control form
/// </summary>
/// <param name="sender">Object that raised the event</param>
/// <param name="e">Event arguments</param>
private void ShowBtn_Click(object sender, EventArgs e) {
var wcf = new WebControlForm();
wcf.Show(this);
}
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Pulire le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione Windows Form
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent() {
this.ShowBtn = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// ShowBtn
//
this.ShowBtn.Location = new System.Drawing.Point(12, 12);
this.ShowBtn.Name = "ShowBtn";
this.ShowBtn.Size = new System.Drawing.Size(134, 40);
this.ShowBtn.TabIndex = 0;
this.ShowBtn.Text = "Show web browser";
this.ShowBtn.UseVisualStyleBackColor = true;
this.ShowBtn.Click += new System.EventHandler(this.ShowBtn_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 261);
this.Controls.Add(this.ShowBtn);
this.Name = "MainForm";
this.Text = "Main form";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button ShowBtn;
}
}
Program.cs
using System;
using System.Diagnostics;
using System.Windows.Forms;
using WebBrowser;
namespace WhosebugIssue {
/// <summary>
/// Main application program
/// </summary>
internal static class Program {
/// <summary>
/// Main application program.
/// </summary>
[STAThread] private static void Main() {
WebBrowserInitializer.Initialize();
Debug.Print("Application started");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="3.2987.1601" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2987.1601" targetFramework="net452" />
<package id="CefSharp.Common" version="57.0.0" targetFramework="net452" />
<package id="CefSharp.WinForms" version="57.0.0" targetFramework="net452" />
</packages>
WebBrowser 项目
CefControl.cs
using System.Windows.Forms;
using CefSharp.WinForms;
namespace WebBrowser {
/// <summary>
/// WebBrowser control
/// </summary>
public class CefControl: UserControl {
public CefControl() {
CefInitializer.Initialize();
InitializeComponent();
var cr = new ChromiumWebBrowser("https://www.google.com");
cr.Dock = DockStyle.Fill;
Controls.Add(cr);
}
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Pulire le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione componenti
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
}
#endregion
}
}
CefInitializer.cs
using CefSharp;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
namespace WebBrowser {
/// <summary>
/// Class that contains the base methods for CEF initializations
/// </summary>
public static class CefInitializer {
/// <summary>
/// Initialize properties
/// </summary>
static CefInitializer() {
CachePath = Path.Combine(Path.GetTempPath(), "SOIssue", "Cache");
LogFile = Path.Combine(Path.GetTempPath(), "SOIssue", "Logs");
UserDataPath = Path.Combine(Path.GetTempPath(), "SOIssue", "Data");
if (!Directory.Exists(CachePath))
Directory.CreateDirectory(CachePath);
if (!Directory.Exists(LogFile))
Directory.CreateDirectory(LogFile);
if (!Directory.Exists(UserDataPath))
Directory.CreateDirectory(UserDataPath);
//Complete the files combine
LogFile = Path.Combine(LogFile, "WebBrowser.log");
AppDomain.CurrentDomain.DomainUnload += (sender, args) => Shutdown();
}
/// <summary>
/// Shutdown all CEF instances
/// </summary>
internal static void Shutdown() {
using (var syncObj = new WindowsFormsSynchronizationContext()) {
syncObj.Send(o => {
if (Cef.IsInitialized)
Cef.Shutdown();
}, new object());
}
}
/// <summary>
/// Initialize CEF libraries
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] internal static void Initialize() {
if (Cef.IsInitialized)
return;
//Get proxy properties
WebProxy proxy = WebRequest.DefaultWebProxy as WebProxy;
string cefPath = Path.GetDirectoryName(Assembly.GetAssembly(typeof(Cef)).Location);
Debug.Print($"CEF Library Path: {cefPath}");
Debug.Assert(cefPath != null, nameof(cefPath) + " != null");
var settings = new CefSettings() {
BrowserSubprocessPath = Path.Combine(cefPath, "CefSharp.BrowserSubprocess.exe"),
LocalesDirPath = Path.Combine(cefPath, "locales"),
ResourcesDirPath = cefPath,
Locale = CultureInfo.CurrentCulture.Name,
CachePath = CachePath,
LogFile = LogFile,
UserDataPath = UserDataPath
};
if (proxy == null || proxy.Address.AbsoluteUri != string.Empty)
settings.CefCommandLineArgs.Add("no-proxy-server", string.Empty);
Cef.Initialize(settings);
}
internal static readonly string CachePath;
internal static readonly string LogFile;
internal static readonly string UserDataPath;
}
}
WebBrowserInitializer.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace WebBrowser {
/// <summary>
/// Class that contains the assembly resolve functions
/// </summary>
public static class WebBrowserInitializer {
private static readonly object _initializer = new object();
private static bool _initialized;
/// <summary>
/// Check if the WebBrowser is initialized
/// </summary>
public static bool IsInitialized {
get {
lock (_initializer)
return _initialized;
}
}
/// <summary>
/// Initialize the current assembly
/// </summary>
public static void Initialize() {
lock (_initializer) {
if (!_initialized) {
AppDomain.CurrentDomain.AssemblyResolve += CefSharp_AssemblyResolve;
_initialized = true;
}
}
}
/// <summary>
/// Try to resolve the assembly
/// </summary>
/// <param name="sender">Object that has raised the event</param>
/// <param name="args">Event raised</param>
/// <returns>Assembly loaded</returns>
private static Assembly CefSharp_AssemblyResolve(object sender, ResolveEventArgs args) {
Debug.Print($"Library: {args.Name}");
if (!args.Name.StartsWith("CefSharp", StringComparison.OrdinalIgnoreCase))
return null;
string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
foreach (var path in GetAssemblyPaths()) {
string checkPath = Path.Combine(path, assemblyName);
if (File.Exists(checkPath)) {
Debug.Print($"Relative path FOUND for {args.Name} in {checkPath}");
return Assembly.UnsafeLoadFrom(checkPath);
}
Debug.Write($"Relative path not found for {args.Name} in {checkPath}");
}
return null;
}
/// <summary>
/// Get all possible assembly paths
/// </summary>
/// <returns>List of possible assembly paths</returns>
private static IEnumerable<string> GetAssemblyPaths() {
string pathPrefix = Environment.Is64BitProcess ? "x64" : "x86";
if (Directory.Exists(@"C:\Program Files (x86)\CEFRuntime\" + pathPrefix))
yield return @"C:\Program Files (x86)\CEFRuntime\" + pathPrefix;
yield return Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, pathPrefix);
yield return Path.Combine(Environment.CurrentDirectory, pathPrefix);
Assembly currentAssembly = Assembly.GetAssembly(typeof(CefInitializer));
if (!string.IsNullOrEmpty(currentAssembly.Location))
yield return Path.Combine(currentAssembly.Location, pathPrefix);
}
}
}
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="3.2987.1601" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2987.1601" targetFramework="net452" />
<package id="CefSharp.Common" version="57.0.0" targetFramework="net452" />
<package id="CefSharp.WinForms" version="57.0.0" targetFramework="net452" />
</packages>
查看 CefSharp 一般用法(https://github.com/cefsharp/CefSharp/wiki/General-Usage#need-to-knowlimitation), I noticed a line that explains that CefSharp works only on default AppDomain. I have looked at the project https://github.com/stever/AppHostCefSharp,我找到了解决方案。
我需要 运行 默认 AppDomain 上的 WebBrowser(我分叉并编辑了 RedGate.AppHost 存储库。请参阅下文了解我这样做的原因。)。为了允许控件之间的通信,我实现了两个 NamedPipes 服务,一个在主窗体上,另一个在创建的对象上。
我发布了完整的解决方案 (https://github.com/rupertsciamenna89/cefsharp-remoting),这样源代码看起来会更简单。它可以改进或修复(就像我的英语 :))
我将原来的项目重命名为更好的名字。
该解决方案由 4 个项目组成:
- MainApplication [旧 WhosebugIssue](我必须使用 ShadowCopy 启动的基本应用程序)
- MainApplication.Launcher [旧 WhosebugIssue.Launcher](应用程序启动器)
- MainApplication.WebBrowser[old WebBrowser](包含WebBrowser的winforms控件库)
- MainApplication.Interfaces(操作必须实现的接口)
MainApplication.Interfaces
该项目包含客户端和服务器必须实现的接口。它包含五个文件:
- IFormService 是允许通过 RedGate.AppHost 创建控件的接口。它包含两个 Guid,用于标识 Control/Server 命名管道的唯一名称。
- IAppClient 是将在 Control 库中实现以对应用程序执行远程调用的客户端接口。
- IAppServer 是将在应用程序中实现以接受来自控件库的远程调用的服务器接口。
- IWebBrowserClient 是将在 Application 中实现以对 Control 库执行远程调用的客户端接口。
- IWebBrowserServer 是将在控件库中实现以接受来自应用程序的远程调用的服务器接口。
MainApplication.WebBrowser
此项目实现初始化 Control WCF 服务的 OutOfProcessEntryPoint 接口。它包含服务器接口的实现,并允许远程客户端显示文件夹并检索返回的结果。
主应用程序
我编辑了 Program.Main 接受二进制路径。我将这个参数保存到一个静态变量中,我将使用它来创建子进程句柄。创建进程句柄的函数是这样的:
public static IChildProcessHandle CreateChildProcessHandle() {
string assemblyPath = _sourcePath ?? Path.GetDirectoryName(Assembly.GetAssembly(typeof(WebBrowserInitializer)).Location);
Debug.Assert(assemblyPath != null, "assemblyPath != null");
var al = new ChildProcessFactory() { ClientExecutablePath = _sourcePath };
return al.Create(Path.Combine(assemblyPath, "MainApplication.WebBrowser.dll"), false, Environment.Is64BitProcess);
}
如果没有传递源路径(比如我直接执行应用程序),RedGate 将使用默认位置(执行程序集路径)。
打开 windows 后,用户可以按 Show(或 ShowDialog)按钮。应用"simply"运行这几行代码:
//Generates client id and server id
string appId = Guid.NewGuid().ToString("N");
string controlId = Guid.NewGuid().ToString("N");
_service = AppServer.Start(appId, controlId);
_service.FormCompleted += Service_FormCompleted;
_locator = new FormServiceLocator(appId, controlId);
_element = _handle.CreateElement(_locator);
_service.StartRemoteClient();
_service.ShowDialog((long)Handle);
当用户关闭window时,回调函数将被调用:
private void Service_FormCompleted(object sender, AppServerEventArgs e) {
//Check if invoke is required
if (InvokeRequired) {
Invoke(new Action<object, AppServerEventArgs>(Service_FormCompleted), sender, e);
return;
}
_element = null;
MessageBox.Show(this, $"Result: {e.Result} - Data: {e.AdditionalData}");
}
MainApplication.Launcher
这是启动启用了 ShadowCopy 的应用程序的项目。我将二进制文件的路径作为参数传递。
var domain = AppDomain.CreateDomain("CefSharp-Remoting",
AppDomain.CurrentDomain.Evidence, setup);
domain.ExecuteAssembly(executablePath, new[] { $"\"/path:{assemblyPath}\"" });
为什么我分叉 RedGate.AppHost 存储库
RedGate.AppHost 尝试找到查看程序集位置的客户端应用程序。启用 ShadowCopy 后,这是不可能的,因为应用程序被复制到 "random" 文件夹中,而客户端应用程序位于源路径中。
我将 ClientExecutablePath 属性 添加到 ChildProcessFactory.cs 和 ProcessStarter.cs 中,因此如果设置了此 属性,ProcessStarter 使用此文件夹而不是默认文件夹。
您可以在以下文件中看到编辑:
我创建了一个使用 ChromiumBrowser 的 Windows 表单应用程序。该应用程序由以下组件组成:
- 主要应用程序
- Web 浏览器库
- 启动器应用程序
当我正常启动我的应用程序时,网络浏览器工作正常。如果我从启动器启动我的应用程序,Web 浏览器将无法工作。它告诉我以下错误:
Unhandled exception of 'System.IO.FileNotFoundException' in Unknown module.
Cannot load file or assembly 'CefSharp, Version=57.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138' or a relative dependency.
Unable to found the specified file.
我不仅需要使用启动器进行更新,而且由于应用程序分布在网络上,有时访问服务器上的文件会出现问题。
问题不仅仅与我的应用有关。我在下面 post 创建了一个测试解决方案,但我遇到了同样的问题。
项目说明
- Cefsharp 运行时位于 C:\Program Files (x86)\CEFRuntime\x64 和 C:\Program Files (x86)\CEFRuntime\x86。 (我创建了一个安装程序来复制这个位置的运行时文件)。运行时基于 NuGet 包。
- 所有可执行文件都在 AnyCpu 中编译(AnyCpu 支持)
Cefsharp 版本 57(Cef redist 3.2987.1601)
运行时内容
x64 文件夹
- cef.pak
- CefSharp.BrowserSubprocess.Core.dll
- CefSharp.BrowserSubprocess.Core.pdb
- CefSharp.BrowserSubprocess.exe
- CefSharp.BrowserSubprocess.pdb
- CefSharp.Core.dll
- CefSharp.Core.pdb
- CefSharp.Core.xml
- CefSharp.dll
- CefSharp.pdb
- CefSharp.WinForms.dll
- CefSharp.WinForms.pdb
- CefSharp.WinForms.XML
- CefSharp.XML
- cef_100_percent.pak
- cef_200_percent.pak
- cef_extensions.pak
- chrome_elf.dll
- d3dcompiler_47.dll
- devtools_resources.pak
- icudtl.dat
- libcef.dll
- libEGL.dll
- libGLESv2.dll
- natives_blob.bin
- snapshot_blob.bin
- widevinecdmadapter.dll
- locales 文件夹(包含所有 .pak)
x86 文件夹
- cef.pak
- CefSharp.BrowserSubprocess.Core.dll
- CefSharp.BrowserSubprocess.Core.pdb
- CefSharp.BrowserSubprocess.exe
- CefSharp.BrowserSubprocess.pdb
- CefSharp.Core.dll
- CefSharp.Core.pdb
- CefSharp.Core.xml
- CefSharp.dll
- CefSharp.pdb
- CefSharp.WinForms.dll
- CefSharp.WinForms.pdb
- CefSharp.WinForms.XML
- CefSharp.XML
- cef_100_percent.pak
- cef_200_percent.pak
- cef_extensions.pak
- chrome_elf.dll
- d3dcompiler_47.dll
- devtools_resources.pak
- icudtl.dat
- libcef.dll
- libEGL.dll
- libGLESv2.dll
- natives_blob.bin
- snapshot_blob.bin
- widevinecdmadapter.dll
- locales 文件夹(包含所有 .pak)
我post给我同样错误的测试方案
测试解决方案
测试方案由三个项目组成:
- WhosebugIssueLauncher
- WhosebugIssue(参考 WebBrowser)
- WebBrowser(包含网络浏览器的 dll 库)
代码如下:
WhosebugIssueLauncher 项目
Program.cs
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
namespace WhosebugIssueLauncher {
/// <summary>
/// Launcher program
/// </summary>
internal static class Program {
/// <summary>
/// Launcher body
/// </summary>
[STAThread, LoaderOptimization(LoaderOptimization.MultiDomainHost)]
private static void Main() {
//Initialize path of application
string startupPath = Environment.CurrentDirectory;
string cachePath = Path.Combine(Path.GetTempPath(), "Program-" + Guid.NewGuid());
string assemblyPath = CanonicalizePathCombine(startupPath, @"..\..\..\WhosebugIssue\bin\Debug\");
string executablePath = Path.Combine(assemblyPath, "WhosebugIssue.exe");
string configFile = executablePath + ".config";
//Start App Domain
try {
var setup = new AppDomainSetup() {
ApplicationName = "WhosebugIssue",
ShadowCopyFiles = "true",
ShadowCopyDirectories = assemblyPath,
CachePath = cachePath,
ConfigurationFile = configFile
};
var domain = AppDomain.CreateDomain("WhosebugIssue", AppDomain.CurrentDomain.Evidence, setup);
domain.ExecuteAssembly(executablePath);
AppDomain.Unload(domain);
}
catch (Exception ex) {
MessageBox.Show(ex.Message, "Warning", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
//Empty cache path
try {
Directory.Delete(cachePath, true);
}
catch (Exception) {
//DO NOTHING
}
}
private static string CanonicalizePathCombine(string sourcePath, string destPath) {
string resultPath = Path.Combine(sourcePath, destPath);
var sb = new StringBuilder(Math.Max(260, 2 * resultPath.Length));
PathCanonicalize(sb, resultPath);
return sb.ToString();
}
[DllImport("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool PathCanonicalize([Out] StringBuilder sb, string src);
}
}
Whosebug 问题项目
WebControlForm.cs
using System.Windows.Forms;
using WebBrowser;
namespace WhosebugIssue {
/// <summary>
/// Form that contains the webbrowser control
/// </summary>
public class WebControlForm : Form {
/// <summary>
/// Create a new web control form
/// </summary>
public WebControlForm() {
InitializeComponent();
Controls.Add(new CefControl { Dock = DockStyle.Fill });
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent() {
this.SuspendLayout();
//
// WebControlForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(529, 261);
this.Name = "WebControlForm";
this.Text = "WebControlForm";
this.ResumeLayout(false);
}
#endregion
}
}
MainForm.cs
using System;
using System.Windows.Forms;
namespace WhosebugIssue {
/// <summary>
/// Main application form
/// </summary>
public partial class MainForm : Form {
/// <summary>
/// Creates the main form
/// </summary>
public MainForm() {
InitializeComponent();
}
/// <summary>
/// Show a new Web Control form
/// </summary>
/// <param name="sender">Object that raised the event</param>
/// <param name="e">Event arguments</param>
private void ShowBtn_Click(object sender, EventArgs e) {
var wcf = new WebControlForm();
wcf.Show(this);
}
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Pulire le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione Windows Form
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent() {
this.ShowBtn = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// ShowBtn
//
this.ShowBtn.Location = new System.Drawing.Point(12, 12);
this.ShowBtn.Name = "ShowBtn";
this.ShowBtn.Size = new System.Drawing.Size(134, 40);
this.ShowBtn.TabIndex = 0;
this.ShowBtn.Text = "Show web browser";
this.ShowBtn.UseVisualStyleBackColor = true;
this.ShowBtn.Click += new System.EventHandler(this.ShowBtn_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 261);
this.Controls.Add(this.ShowBtn);
this.Name = "MainForm";
this.Text = "Main form";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button ShowBtn;
}
}
Program.cs
using System;
using System.Diagnostics;
using System.Windows.Forms;
using WebBrowser;
namespace WhosebugIssue {
/// <summary>
/// Main application program
/// </summary>
internal static class Program {
/// <summary>
/// Main application program.
/// </summary>
[STAThread] private static void Main() {
WebBrowserInitializer.Initialize();
Debug.Print("Application started");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}
}
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="3.2987.1601" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2987.1601" targetFramework="net452" />
<package id="CefSharp.Common" version="57.0.0" targetFramework="net452" />
<package id="CefSharp.WinForms" version="57.0.0" targetFramework="net452" />
</packages>
WebBrowser 项目
CefControl.cs
using System.Windows.Forms;
using CefSharp.WinForms;
namespace WebBrowser {
/// <summary>
/// WebBrowser control
/// </summary>
public class CefControl: UserControl {
public CefControl() {
CefInitializer.Initialize();
InitializeComponent();
var cr = new ChromiumWebBrowser("https://www.google.com");
cr.Dock = DockStyle.Fill;
Controls.Add(cr);
}
/// <summary>
/// Variabile di progettazione necessaria.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Pulire le risorse in uso.
/// </summary>
/// <param name="disposing">ha valore true se le risorse gestite devono essere eliminate, false in caso contrario.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Codice generato da Progettazione componenti
/// <summary>
/// Metodo necessario per il supporto della finestra di progettazione. Non modificare
/// il contenuto del metodo con l'editor di codice.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
}
#endregion
}
}
CefInitializer.cs
using CefSharp;
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
namespace WebBrowser {
/// <summary>
/// Class that contains the base methods for CEF initializations
/// </summary>
public static class CefInitializer {
/// <summary>
/// Initialize properties
/// </summary>
static CefInitializer() {
CachePath = Path.Combine(Path.GetTempPath(), "SOIssue", "Cache");
LogFile = Path.Combine(Path.GetTempPath(), "SOIssue", "Logs");
UserDataPath = Path.Combine(Path.GetTempPath(), "SOIssue", "Data");
if (!Directory.Exists(CachePath))
Directory.CreateDirectory(CachePath);
if (!Directory.Exists(LogFile))
Directory.CreateDirectory(LogFile);
if (!Directory.Exists(UserDataPath))
Directory.CreateDirectory(UserDataPath);
//Complete the files combine
LogFile = Path.Combine(LogFile, "WebBrowser.log");
AppDomain.CurrentDomain.DomainUnload += (sender, args) => Shutdown();
}
/// <summary>
/// Shutdown all CEF instances
/// </summary>
internal static void Shutdown() {
using (var syncObj = new WindowsFormsSynchronizationContext()) {
syncObj.Send(o => {
if (Cef.IsInitialized)
Cef.Shutdown();
}, new object());
}
}
/// <summary>
/// Initialize CEF libraries
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)] internal static void Initialize() {
if (Cef.IsInitialized)
return;
//Get proxy properties
WebProxy proxy = WebRequest.DefaultWebProxy as WebProxy;
string cefPath = Path.GetDirectoryName(Assembly.GetAssembly(typeof(Cef)).Location);
Debug.Print($"CEF Library Path: {cefPath}");
Debug.Assert(cefPath != null, nameof(cefPath) + " != null");
var settings = new CefSettings() {
BrowserSubprocessPath = Path.Combine(cefPath, "CefSharp.BrowserSubprocess.exe"),
LocalesDirPath = Path.Combine(cefPath, "locales"),
ResourcesDirPath = cefPath,
Locale = CultureInfo.CurrentCulture.Name,
CachePath = CachePath,
LogFile = LogFile,
UserDataPath = UserDataPath
};
if (proxy == null || proxy.Address.AbsoluteUri != string.Empty)
settings.CefCommandLineArgs.Add("no-proxy-server", string.Empty);
Cef.Initialize(settings);
}
internal static readonly string CachePath;
internal static readonly string LogFile;
internal static readonly string UserDataPath;
}
}
WebBrowserInitializer.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace WebBrowser {
/// <summary>
/// Class that contains the assembly resolve functions
/// </summary>
public static class WebBrowserInitializer {
private static readonly object _initializer = new object();
private static bool _initialized;
/// <summary>
/// Check if the WebBrowser is initialized
/// </summary>
public static bool IsInitialized {
get {
lock (_initializer)
return _initialized;
}
}
/// <summary>
/// Initialize the current assembly
/// </summary>
public static void Initialize() {
lock (_initializer) {
if (!_initialized) {
AppDomain.CurrentDomain.AssemblyResolve += CefSharp_AssemblyResolve;
_initialized = true;
}
}
}
/// <summary>
/// Try to resolve the assembly
/// </summary>
/// <param name="sender">Object that has raised the event</param>
/// <param name="args">Event raised</param>
/// <returns>Assembly loaded</returns>
private static Assembly CefSharp_AssemblyResolve(object sender, ResolveEventArgs args) {
Debug.Print($"Library: {args.Name}");
if (!args.Name.StartsWith("CefSharp", StringComparison.OrdinalIgnoreCase))
return null;
string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll";
foreach (var path in GetAssemblyPaths()) {
string checkPath = Path.Combine(path, assemblyName);
if (File.Exists(checkPath)) {
Debug.Print($"Relative path FOUND for {args.Name} in {checkPath}");
return Assembly.UnsafeLoadFrom(checkPath);
}
Debug.Write($"Relative path not found for {args.Name} in {checkPath}");
}
return null;
}
/// <summary>
/// Get all possible assembly paths
/// </summary>
/// <returns>List of possible assembly paths</returns>
private static IEnumerable<string> GetAssemblyPaths() {
string pathPrefix = Environment.Is64BitProcess ? "x64" : "x86";
if (Directory.Exists(@"C:\Program Files (x86)\CEFRuntime\" + pathPrefix))
yield return @"C:\Program Files (x86)\CEFRuntime\" + pathPrefix;
yield return Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, pathPrefix);
yield return Path.Combine(Environment.CurrentDirectory, pathPrefix);
Assembly currentAssembly = Assembly.GetAssembly(typeof(CefInitializer));
if (!string.IsNullOrEmpty(currentAssembly.Location))
yield return Path.Combine(currentAssembly.Location, pathPrefix);
}
}
}
packages.config
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="cef.redist.x64" version="3.2987.1601" targetFramework="net452" />
<package id="cef.redist.x86" version="3.2987.1601" targetFramework="net452" />
<package id="CefSharp.Common" version="57.0.0" targetFramework="net452" />
<package id="CefSharp.WinForms" version="57.0.0" targetFramework="net452" />
</packages>
查看 CefSharp 一般用法(https://github.com/cefsharp/CefSharp/wiki/General-Usage#need-to-knowlimitation), I noticed a line that explains that CefSharp works only on default AppDomain. I have looked at the project https://github.com/stever/AppHostCefSharp,我找到了解决方案。
我需要 运行 默认 AppDomain 上的 WebBrowser(我分叉并编辑了 RedGate.AppHost 存储库。请参阅下文了解我这样做的原因。)。为了允许控件之间的通信,我实现了两个 NamedPipes 服务,一个在主窗体上,另一个在创建的对象上。
我发布了完整的解决方案 (https://github.com/rupertsciamenna89/cefsharp-remoting),这样源代码看起来会更简单。它可以改进或修复(就像我的英语 :))
我将原来的项目重命名为更好的名字。
该解决方案由 4 个项目组成:
- MainApplication [旧 WhosebugIssue](我必须使用 ShadowCopy 启动的基本应用程序)
- MainApplication.Launcher [旧 WhosebugIssue.Launcher](应用程序启动器)
- MainApplication.WebBrowser[old WebBrowser](包含WebBrowser的winforms控件库)
- MainApplication.Interfaces(操作必须实现的接口)
MainApplication.Interfaces
该项目包含客户端和服务器必须实现的接口。它包含五个文件:
- IFormService 是允许通过 RedGate.AppHost 创建控件的接口。它包含两个 Guid,用于标识 Control/Server 命名管道的唯一名称。
- IAppClient 是将在 Control 库中实现以对应用程序执行远程调用的客户端接口。
- IAppServer 是将在应用程序中实现以接受来自控件库的远程调用的服务器接口。
- IWebBrowserClient 是将在 Application 中实现以对 Control 库执行远程调用的客户端接口。
- IWebBrowserServer 是将在控件库中实现以接受来自应用程序的远程调用的服务器接口。
MainApplication.WebBrowser
此项目实现初始化 Control WCF 服务的 OutOfProcessEntryPoint 接口。它包含服务器接口的实现,并允许远程客户端显示文件夹并检索返回的结果。
主应用程序
我编辑了 Program.Main 接受二进制路径。我将这个参数保存到一个静态变量中,我将使用它来创建子进程句柄。创建进程句柄的函数是这样的:
public static IChildProcessHandle CreateChildProcessHandle() {
string assemblyPath = _sourcePath ?? Path.GetDirectoryName(Assembly.GetAssembly(typeof(WebBrowserInitializer)).Location);
Debug.Assert(assemblyPath != null, "assemblyPath != null");
var al = new ChildProcessFactory() { ClientExecutablePath = _sourcePath };
return al.Create(Path.Combine(assemblyPath, "MainApplication.WebBrowser.dll"), false, Environment.Is64BitProcess);
}
如果没有传递源路径(比如我直接执行应用程序),RedGate 将使用默认位置(执行程序集路径)。
打开 windows 后,用户可以按 Show(或 ShowDialog)按钮。应用"simply"运行这几行代码:
//Generates client id and server id
string appId = Guid.NewGuid().ToString("N");
string controlId = Guid.NewGuid().ToString("N");
_service = AppServer.Start(appId, controlId);
_service.FormCompleted += Service_FormCompleted;
_locator = new FormServiceLocator(appId, controlId);
_element = _handle.CreateElement(_locator);
_service.StartRemoteClient();
_service.ShowDialog((long)Handle);
当用户关闭window时,回调函数将被调用:
private void Service_FormCompleted(object sender, AppServerEventArgs e) {
//Check if invoke is required
if (InvokeRequired) {
Invoke(new Action<object, AppServerEventArgs>(Service_FormCompleted), sender, e);
return;
}
_element = null;
MessageBox.Show(this, $"Result: {e.Result} - Data: {e.AdditionalData}");
}
MainApplication.Launcher
这是启动启用了 ShadowCopy 的应用程序的项目。我将二进制文件的路径作为参数传递。
var domain = AppDomain.CreateDomain("CefSharp-Remoting",
AppDomain.CurrentDomain.Evidence, setup);
domain.ExecuteAssembly(executablePath, new[] { $"\"/path:{assemblyPath}\"" });
为什么我分叉 RedGate.AppHost 存储库
RedGate.AppHost 尝试找到查看程序集位置的客户端应用程序。启用 ShadowCopy 后,这是不可能的,因为应用程序被复制到 "random" 文件夹中,而客户端应用程序位于源路径中。
我将 ClientExecutablePath 属性 添加到 ChildProcessFactory.cs 和 ProcessStarter.cs 中,因此如果设置了此 属性,ProcessStarter 使用此文件夹而不是默认文件夹。
您可以在以下文件中看到编辑: