使用Shadow Copy

时间:2017-11-21 13:49:07

标签: c# .net winforms cefsharp

我创建了一个使用ChromiumBrowser的Windows窗体应用程序。该应用程序由以下组件组成:

  • 主要应用
  • 网络浏览器库
  • 启动器应用程序

当我正常启动应用程序时,Web浏览器可以正常运行。如果我从启动器启动我的应用程序,则Web浏览器不起作用。它告诉我以下错误:

  

System.IO.FileNotFoundException' System.IO.FileNotFoundException'未处理的异常在未知模块中。

     

无法加载文件或程序集' CefSharp,Version = 57.0.0.0,Culture = neutral,PublicKeyToken = 40c4b6fc221f4138'或相对依赖。

     

无法找到指定的文件。

我需要使用启动器不仅用于更新,而且由于应用程序是在网络上分发的,因此有时在服务器上访问文件时会出现问题。

问题不仅仅与我的应用有关。我创建了一个我在下面发布的测试解决方案,但我遇到了同样的问题。

项目说明

  • 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文件夹(包含所有.paks)

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文件夹(包含所有.paks)

我发布的测试解决方案给出了同样的错误。

测试解决方案

测试解决方案由三个项目组成:

  • StackOverflowIssueLauncher
  • StackOverflowIssue(对WebBrowser的引用)
  • WebBrowser(包含webbrowser的dll库)

代码如下所示:

Project StackOverflowIssueLauncher

Program.cs的

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace StackOverflowIssueLauncher {

    /// <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, @"..\..\..\StackOverflowIssue\bin\Debug\");
            string executablePath = Path.Combine(assemblyPath, "StackOverflowIssue.exe");
            string configFile = executablePath + ".config";

            //Start App Domain
            try {
                var setup = new AppDomainSetup() {
                    ApplicationName = "StackOverflowIssue",
                    ShadowCopyFiles = "true",
                    ShadowCopyDirectories = assemblyPath,
                    CachePath = cachePath,
                    ConfigurationFile = configFile
                };

                var domain = AppDomain.CreateDomain("StackOverflowIssue", 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);
    }
}

Project StackOverflowIssue

WebControlForm.cs

using System.Windows.Forms;
using WebBrowser;

namespace StackOverflowIssue {

    /// <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 StackOverflowIssue {

    /// <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 StackOverflowIssue {

    /// <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>

Project 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>

1 个答案:

答案 0 :(得分:1)

查看CefSharp常规用法(https://github.com/cefsharp/CefSharp/wiki/General-Usage#need-to-knowlimitation),我注意到一行说明CefSharp在默认AppDomain上只能 。我查看了项目https://github.com/stever/AppHostCefSharp,我找到了解决方案。

我需要在默认AppDomain上运行WebBrowser(我分叉并编辑了Re​​dGate.AppHost存储库。请参阅下面我为什么这样做。)。为了允许控件之间的通信,我实现了两个NamedPipes服务,一个在主窗体上,另一个在创建的对象上。

我发布了完整的解决方案(https://github.com/rupertsciamenna89/cefsharp-remoting),因此源代码将更加简单易懂。它可以改进或修复(如我的英文:))

我将原始项目重命名为更好的名称。

该解决方案由4个项目组成:

  • MainApplication [旧StackOverflowIssue](我必须使用ShadowCopy启动的基本应用程序)
  • MainApplication.Launcher [旧StackOverflowIssue.Launcher](应用程序启动器)
  • MainApplication.WebBrowser [旧WebBrowser](包含WebBrowser的winforms控件库)
  • MainApplication.Interfaces(必须为操作实现的接口)

MainApplication.Interfaces

此项目包含客户端和服务器必须实现的接口。它包含五个文件:

  • IFormService 是允许通过RedGate.AppHost创建控件的界面。它包含两个Guid,用于标识Control / Server命名管道的唯一名称。
  • IAppClient 是将在Control库中实现的客户端界面,用于对Application执行远程调用。
  • IAppServer 是将在Application中实现的服务器接口,以接受来自Control库的远程调用。
  • IWebBrowserClient 是将在Application中实现以在Control库上执行远程调用的客户端界面。
  • IWebBrowserServer 是将在Control libray中实现以接受来自Application的远程调用的服务器接口。

MainApplication.WebBrowser

此项目实现初始化Control WCF服务的OutOfProcessEntryPoint接口。它包含Server接口的实现,并允许远程客户端显示该文件夹并检索返回的结果。

MainApplication

我编辑了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将使用默认位置(正在执行的程序集路径)。

打开窗口后,用户可以按Show(或ShowDialog)按钮。应用程序&#34;简单地&#34;运行以下代码行:

//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);

当用户关闭窗口时,将调用回调函数:

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尝试找到Clients应用程序查看Assembly位置。启用S​​hadowCopy后,这是不可能的,因为应用程序被复制到&#34;随机&#34;文件夹,客户端应用程序位于源路径中。

我将ClientExecutablePath属性添加到ChildProcessFactory.cs和ProcessStarter.cs中,因此如果设置了此属性,ProcessStarter将使用此文件夹而不是默认文件夹。

您可以在以下文件中看到这些修改: