I am trying to embed WebView2 DLL in a C# project. I have added the 3 DLLs :
Microsoft.Web.WebView2.Wpf.dll
Microsoft.Web.WebView2.Core.dll
WebView2Loader.dll
as embedded resource to my project. Since it is a WPF project I have no need of Microsoft.Web.WebView2.Forms.dll.
I have already Newtonsoft.Json.dll correctly incorporated.
In debug mode, there is no problem because all DLL are copied to the output directory.
As I want that the executable to be portable, I want only a single file (I know that copying the DLL with the exe will work, but I need a single file). When I move the main executable only to another folder, the application crashes when I use the webview2 control (the control is not loaded at start).
I have tried to figure out if all the DLL were involved. Microsoft.Web.WebView2.Wpf.dll is correctly embedded and I have no need of it in the destination folder.
However, the last two ones are still required. I think this is probably because they are called by Microsoft.Web.WebView2.Wpf.dll and not by the main assemby.
How can I load the last two ones from the main assembly to have a single execution file ?
EDIT: All DLL are added to the project as embedded resource
EDIT2
Thanks to @mm8 to give me ideas to partialy (any suggestions for complete solution are welcomed) achieve this.
So, It is possible to embed the 3 WebView2 DLL into you project by adding them and setting them as embedded resource. Microsoft.Web.WebView2.Wpf.dll
(or Microsoft.Web.WebView2.WinForms.dll
, depending on your choice) can be loaded directly into memory. But the last two ones (Microsoft.Web.WebView2.Core.dll
and WebView2Loader.dll
) need to be saved as files :
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
}
}
}
The ExtractWebDLLFromAssembly
method is based from this post @nawfal's answer
As the ExtractWebDLLFromAssembly
method is called only once, its code can also be directly integrated in MainWindow
.
One thing is still missing :
I am facing issues when I try to delete these files in the MainWindow_OnClosing
event saying that files are in use by another process (in fact, the main process), even if the control is not loaded or is disposed.
I am using this code to be sure that CoreWebView2 was unloaded inside the control :
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 { }
This code allows me to correctly delete the UserDataFolder, but the DLL are still in use.
After further inspection, I know that Microsoft.Web.WebView2.Core.dll
is still loaded as an assembly in the CurrentAppDomain
which is obviously a problem.
And WebView2Loader.dll
is loaded as a module of the Process.
If I try to force to unload it with
[DllImport("kernel32.dll", SetLastError = true)]
public static extern void FreeLibrary(IntPtr module)
this does not work. I think this is because Microsoft.Web.WebView2.Core.dll
use it. And this should be the root problem left for me.
EDIT 3
I have tried to load the WebView control in an other thread. I don't need to retrieve values from the control. I only need to set the source on code behind.
I have tried several things without any success, the source of the threaded control is not updated (the EnsureCoreWebView2Async
method called after the control Loaded
event stays awaited indefinitly).
The main idea to achieve this is inspired from here
EDIT 4
Ok, I manage one file :). WebView2Loader.dll
can be unloaded with FreeLibrary
contrary that what I said in edit 2, I was trying to unload it after the Dispose()
method, while you need to wait the dispose is completed. So you need to call it after the catch
block
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 { }
The UnloadModule
method is taken from this, @SI4's answer
The last dll, Microsoft.Web.WebView2.Core.dll
is opened within the CurrentAppDomain
and cannot unloaded this way nor be deleted. I should probably load it within a new AppDomain
so that I can unload it. However, I don't know how to achieve this with the ability to change the Source
property of the control. May be the whole code should be loaded in a new AppDomain
?
EDIT 5
I have found a new option that could be the solution : WPF Add-In
I have created the files from the example, the user control contains WebView2 instead of a button. However, for both AddInAdapter
and HostAdapter
, FrameworkElementAdapters
is marked with an error "The name does not exist in the current context" ??? (References has been added to the project which is targetting .Net 4.6.2). Any clue ?
But, it seems that application needs to have different folders. If this is the case, this is a wrong turn. I still need one executable and nothing more.
Thanks