将 WebView2 DLL 嵌入到 wpf 可移植可执行文件中

CFo*_*Fou 7 c# wpf

我正在尝试将 WebView2 DLL 嵌入到 C# 项目中。我添加了 3 个 DLL:

Microsoft.Web.WebView2.Wpf.dll
Microsoft.Web.WebView2.Core.dll
WebView2Loader.dll
Run Code Online (Sandbox Code Playgroud)

作为我的项目的嵌入式资源。由于它是一个 WPF 项目,我不需要 Microsoft.Web.WebView2.Forms.dll。

我已经正确合并了 Newtonsoft.Json.dll。

在调试模式下,没有问题,因为所有DLL都被复制到输出目录。

因为我希望可执行文件是可移植的,所以我只需要一个文件(我知道用 exe 复制 DLL 是可行的,但我需要一个文件)。当我仅将主可执行文件移动到另一个文件夹时,当我使用 webview2 控件时应用程序崩溃(该控件在启动时未加载)。

我试图弄清楚是否所有的 DLL 都参与其中。Microsoft.Web.WebView2.Wpf.dll 已正确嵌入,我不需要在目标文件夹中使用它。

然而,最后两个仍然是必需的。我认为这可能是因为它们是由 Microsoft.Web.WebView2.Wpf.dll 调用的,而不是由主程序集调用的。

如何从主程序集中加载最后两个文件以获得单个执行文件?

编辑:所有 DLL 都作为嵌入式资源添加到项目中

编辑2

感谢@mm8给我部分想法(欢迎任何完整解决方案的建议)实现这一目标。

因此,可以通过添加 3 个 WebView2 DLL 并将它们设置为嵌入资源来将它们嵌入到您的项目中。Microsoft.Web.WebView2.Wpf.dll(或者Microsoft.Web.WebView2.WinForms.dll,取决于您的选择)可以直接加载到内存中。但最后两个(Microsoft.Web.WebView2.Core.dllWebView2Loader.dll)需要保存为文件:

public MainWindow()
{
   // For Newtonsoft.Json.dll, Microsoft.Web.WebView2.Wpf.dll, Microsoft.Web.WebView2.Core.dll and WebView2Loader.dll
   // Incorporation as embedded resource
   // To avoid having multiple files, only one executable
   AppDomain.CurrentDomain.AssemblyResolve += GetEmbeddedDll;
   ExtractWebDLLFromAssembly();

   InitializeComponent();
}

private byte[] GetAssemblyBytes(string assemblyResource)
{
    using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(assemblyResource))
    {
        return new BinaryReader(stream).ReadBytes((int)stream.Length);
    }
}

// For Newtonsoft.Json.dll and Microsoft.Web.WebView2.Wpf.dll
private Assembly GetEmbeddedDll(object sender, ResolveEventArgs args)
{
    string keyName = new AssemblyName(args.Name).Name;

    // If DLL is loaded then don't load it again just return
    if (_libs.ContainsKey(keyName))
    {
        return _libs[keyName];
    }

    Assembly assembly = Assembly.Load(GetAssemblyBytes(Assembly.GetExecutingAssembly().GetName().Name + "." + keyName + ".dll"));
    _libs[keyName] = assembly;
    return assembly;
}

// For Microsoft.Web.WebView2.Core.dll and WebView2Loader.dll
// Incorporation as embedded resource but need to be extracted
private void ExtractWebDLLFromAssembly()
{
    string[] dllNames = new string[] { "Microsoft.Web.WebView2.Core.dll", "WebView2Loader.dll" };
    string thisAssemblyName = Assembly.GetExecutingAssembly().GetName().Name;
    foreach (string dllName in dllNames)
    {
        string dllFullPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, dllName);
        try
        {
            File.WriteAllBytes(dllFullPath, GetAssemblyBytes(thisAssemblyName + "." + dllName));
        }
        catch
        {
            // Depending on what you want to do, continue or exit
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

ExtractWebDLLFromAssembly方法基于这篇文章 @nawfal的回答

由于该ExtractWebDLLFromAssembly方法仅被调用一次,因此其代码也可以直接集成到MainWindow.

还缺少一件事:

当我尝试删除这些文件时,MainWindow_OnClosing如果说文件正在被另一个进程(实际上是主进程)使用,即使控件未加载或已释放,我也会遇到问题。我使用此代码来确保 CoreWebView2 已在控件内卸载:

int maxTimeout = 2000;
uint wvProcessId = wv_ctrl.CoreWebView2.BrowserProcessId;
string userDataFolder = wv_ctrl.CoreWebView2.Environment.UserDataFolder;
int timeout = 10;
wv_ctrl.Dispose();
try
{
    while (System.Diagnostics.Process.GetProcessById(Convert.ToInt32(wvProcessId)) != null && timeout < maxTimeout)
    {
        System.Threading.Thread.Sleep(10);
        timeout += 10;
    }
}
catch { }
Run Code Online (Sandbox Code Playgroud)

这段代码允许我正确删除 UserDataFolder,但 DLL 仍在使用中。

经过进一步检查,我知道Microsoft.Web.WebView2.Core.dll仍然作为程序集加载,这CurrentAppDomain显然是有问题的。
WebView2Loader.dll作为 Process 的模块加载。如果我尝试强制卸载它

 [DllImport("kernel32.dll", SetLastError = true)]
 public static extern void FreeLibrary(IntPtr module)
Run Code Online (Sandbox Code Playgroud)

这是行不通的。我想这是因为Microsoft.Web.WebView2.Core.dll使用它。而这应该是留给我的根本问题。

编辑3

我尝试在其他线程中加载 WebView 控件。我不需要从控件中检索值。我只需要在代码后面设置源即可。我已经尝试了几件事,但没有成功,线程控件的源未更新(EnsureCoreWebView2Async控件Loaded事件后调用的方法无限期等待)。实现这一目标的主要想法是受到这里的启发

编辑4

好的,我管理一个文件:)。WebView2Loader.dll可以与FreeLibrary我在编辑2中所说的相反的方式卸载,我试图在Dispose()方法之后卸载它,而你需要等待处理完成。catch所以你需要在块之后调用它

int maxTimeout = 2000;
uint wvProcessId = wv_ctrl.CoreWebView2.BrowserProcessId;
string userDataFolder = wv_ctrl.CoreWebView2.Environment.UserDataFolder;
int timeout = 10;
wv_ctrl.Dispose();
try
{
    while (System.Diagnostics.Process.GetProcessById(Convert.ToInt32(wvProcessId)) != null && timeout < maxTimeout)
    {
        System.Threading.Thread.Sleep(10);
        timeout += 10;
    }
}
catch { }
UnloadModule("WebView2Loader.dll");
if (Settings.Default.DeleteBrowserNavigationData)
{
    try
    {
        Directory.Delete(userDataFolder, true);
    }
    catch { }
}
// Delete WebView2 DLLs (2nd line throws an error)
try
{
    File.Delete(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "WebView2Loader.dll"));
    File.Delete(Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "Microsoft.Web.WebView2.Core.dll"));
 }
 catch { }
Run Code Online (Sandbox Code Playgroud)

方法UnloadModule取自@SI4的回答

最后一个 dllMicrosoft.Web.WebView2.Core.dll在 内打开,CurrentAppDomain无法以这种方式卸载也无法删除。我可能应该将它加载到一个新的中AppDomain,以便我可以卸载它。但是,我不知道如何通过更改Source控件的属性来实现这一点。可能整个代码应该加载到一个新的AppDomain

编辑5

我找到了一个可能作为解决方案的新选项:WPF Add-In 我已经从示例中创建了文件,用户控件包含 WebView2 而不是按钮。但是,对于 和AddInAdapterHostAdapterFrameworkElementAdapters标有错误“该名称在当前上下文中不存在”??? (参考文献已添加到针对 .Net 4.6.2 的项目中)。有什么线索吗?但是,应用程序似乎需要有不同的文件夹。如果是这样的话,这就是一个错误的转折。我仍然需要一个可执行文件,仅此而已。

谢谢

小智 3

如果您的应用程序不需要针对尚未集成此功能的特定框架,您可以选择使用自 .NET Core 3.0 以来可用的单文件应用程序

我尝试过使用 WebView2 的 WPF 应用程序,通过此方法,您可以在同一个 .exe 文件中发布应用程序和引用。

由于 WebView2 使用本机库,因此您只需在 .csproj 文件的 PropertyGroup 中添加以下行:

<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
Run Code Online (Sandbox Code Playgroud)