Estoy tratando de incrustar WebView2 DLL en un proyecto de C#. He agregado las 3 DLL:
Microsoft.Web.WebView2.Wpf.dll Microsoft.Web.WebView2.Core.dll WebView2Loader.dllcomo recurso integrado a mi proyecto. Dado que es un proyecto WPF, no necesito Microsoft.Web.WebView2.Forms.dll.
Ya tengo Newtonsoft.Json.dll correctamente incorporado.
En el modo de depuración, no hay problema porque todas las DLL se copian en el directorio de salida.
Como quiero que el ejecutable sea portátil, quiero un solo archivo (sé que copiar la DLL con el exe funcionará, pero necesito un solo archivo). Cuando muevo el ejecutable principal solo a otra carpeta, la aplicación falla cuando uso el control webview2 (el control no se carga al inicio).
He intentado averiguar si todas las DLL estaban involucradas. Microsoft.Web.WebView2.Wpf.dll está correctamente incrustado y no lo necesito en la carpeta de destino.
Sin embargo, los dos últimos todavía son necesarios. Creo que esto probablemente se deba a que Microsoft.Web.WebView2.Wpf.dll los llama y no el ensamblaje principal.
¿Cómo puedo cargar los dos últimos del ensamblaje principal para tener un solo archivo de ejecución?
EDITAR: todas las DLL se agregan al proyecto como recurso incrustado
Gracias a @ mm8 por darme ideas para lograr esto parcialmente (cualquier sugerencia para una solución completa es bienvenida).
Por lo tanto, es posible incrustar las 3 DLL de WebView2 en su proyecto agregándolas y configurándolas como recurso incrustado. Microsoft.Web.WebView2.Wpf.dll (o Microsoft.Web.WebView2.WinForms.dll , según su elección) se puede cargar directamente en la memoria. Pero los dos últimos ( Microsoft.Web.WebView2.Core.dll y WebView2Loader.dll ) deben guardarse como archivos:
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 } } } El método ExtractWebDLLFromAssembly se basa en la respuesta de esta publicación @nawfal
Como el método ExtractWebDLLFromAssembly se llama solo una vez, su código también se puede integrar directamente en MainWindow .
Tengo problemas cuando trato de eliminar estos archivos en el evento MainWindow_OnClosing que dice que otro proceso (de hecho, el proceso principal) está usando los archivos, incluso si el control no está cargado o se elimina. Estoy usando este código para asegurarme de que CoreWebView2 se descargó dentro del 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 { }Este código me permite eliminar correctamente UserDataFolder, pero las DLL todavía están en uso.
Después de una inspección más detallada, sé que Microsoft.Web.WebView2.Core.dll todavía está cargado como un ensamblado en CurrentAppDomain , lo que obviamente es un problema.
Y WebView2Loader.dll se carga como un módulo del Proceso. Si trato de forzar a descargarlo con
[DllImport("kernel32.dll", SetLastError = true)] public static extern void FreeLibrary(IntPtr module) esto no funciona. Creo que esto se debe a que Microsoft.Web.WebView2.Core.dll lo usa. Y este debería ser el problema de raíz que me queda.
He intentado cargar el control WebView en otro hilo. No necesito recuperar valores del control. Solo necesito configurar la fuente en el código detrás. He intentado varias cosas sin ningún éxito, la fuente del control enhebrado no se actualiza (el método EnsureCoreWebView2Async llamado después del evento Loaded del control se espera indefinidamente). La idea principal para lograr esto está inspirada en aquí.
Ok, administro un archivo :). WebView2Loader.dll se puede descargar con FreeLibrary contrario a lo que dije en la edición 2, estaba tratando de descargarlo después del método Dispose() , mientras que debe esperar a que se complete la eliminación. Así que necesitas llamarlo después del bloque 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 { } El método UnloadModule se toma de esto, la respuesta de @ SI4
El último dll, Microsoft.Web.WebView2.Core.dll , se abre dentro de CurrentAppDomain y no puede descargarse de esta manera ni eliminarse. Probablemente debería cargarlo dentro de un nuevo AppDomain para poder descargarlo. Sin embargo, no sé cómo lograr esto con la capacidad de cambiar la propiedad Source del control. ¿Puede ser que todo el código deba cargarse en un nuevo AppDomain ?
Encontré una nueva opción que podría ser la solución: Complemento WPF He creado los archivos del ejemplo, el control de usuario contiene WebView2 en lugar de un botón. Sin embargo, tanto para AddInAdapter como para HostAdapter , FrameworkElementAdapters está marcado con un error "El nombre no existe en el contexto actual" ??? (Se agregaron referencias al proyecto que apunta a .Net 4.6.2). Cualquier pista ? Pero, parece que la aplicación necesita tener carpetas diferentes. Si este es el caso, este es un giro equivocado. Todavía necesito un ejecutable y nada más.
Gracias
Si su aplicación no necesita apuntar a un marco particular que aún no integra esta funcionalidad, tiene la opción de usar la aplicación de archivo único disponible desde .NET Core 3.0.
He probado con una aplicación WPF usando un WebView2, y con este método puedes publicar la aplicación y las referencias en el mismo archivo .exe.
Dado que WebView2 funciona con bibliotecas nativas, solo necesita agregar la siguiente línea en su PropertyGroup de su archivo .csproj:
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>