【发布时间】:2022-02-26 01:39:00
【问题描述】:
我正在尝试将 WebView2 DLL 嵌入到 C# 项目中。我已经添加了 3 个 DLL:
Microsoft.Web.WebView2.Wpf.dll
Microsoft.Web.WebView2.Core.dll
WebView2Loader.dll
作为我项目的嵌入式资源。 因为它是一个 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.dll和WebView2Loader.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
}
}
}
ExtractWebDLLFromAssembly 方法基于this post @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 { }
此代码允许我正确删除 UserDataFolder,但 DLL 仍在使用中。
经过进一步检查,我知道Microsoft.Web.WebView2.Core.dll 仍然作为程序集加载到CurrentAppDomain 中,这显然是一个问题。
并且WebView2Loader.dll 被加载为进程的一个模块。
如果我试图强制卸载它
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void FreeLibrary(IntPtr module)
这不起作用。我认为这是因为Microsoft.Web.WebView2.Core.dll 使用它。这应该是留给我的根本问题。
编辑 3
我试图在另一个线程中加载 WebView 控件。我不需要从控件中检索值。我只需要在后面的代码上设置源代码。
我尝试了几件事但没有成功,线程控件的源没有更新(在控件Loaded 事件之后调用的EnsureCoreWebView2Async 方法无限期地等待)。
实现这一点的主要思想来自here
编辑 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 { }
UnloadModule方法取自this, @SI4's answer
最后一个 dll,Microsoft.Web.WebView2.Core.dll 在CurrentAppDomain 中打开,不能以这种方式卸载,也不能被删除。我可能应该在新的AppDomain 中加载它,以便我可以卸载它。但是,我不知道如何通过更改控件的Source 属性来实现这一点。可能整个代码应该加载到一个新的AppDomain 中?
编辑 5
我找到了一个可能是解决方案的新选项:WPF Add-In
我已经从示例中创建了文件,用户控件包含 WebView2 而不是按钮。但是,对于AddInAdapter 和HostAdapter,FrameworkElementAdapters 都标记有错误“该名称在当前上下文中不存在”??? (参考资料已添加到针对 .Net 4.6.2 的项目中)。有什么线索吗?
但是,似乎应用程序需要有不同的文件夹。如果是这样的话,这是一个错误的转折。我仍然需要一个可执行文件,仅此而已。
谢谢
【问题讨论】:
-
如何创建复制到目标机器的主要可执行文件?
-
我只是在 Visual Studio 中构建解决方案(Ctrl-Shift-B)
-
那么程序集不会嵌入到
.exe中。这不是有效的部署。 -
因为exe的大小不一样,另外,这适用于Newtonsoft.Json.dll和Microsoft.Web.WebView2.Wpf.dll。我不需要这些。我的问题是 Microsoft.Web.WebView2.Core.dll 和 WebView2Loader.dll (见我的编辑)
-
除非你不使用这些,否则你怎么知道它“有效”?`