diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTXW.cs b/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTXW.cs index 9fe20f92dad..8fb083e6995 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTXW.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTXW.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -9,6 +9,7 @@ internal partial class Interop { internal partial class Kernel32 { + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public unsafe struct ACTCTXW { public uint cbSize; @@ -19,6 +20,7 @@ public unsafe struct ACTCTXW public char* lpAssemblyDirectory; public IntPtr lpResourceName; public char* lpApplicationName; + public IntPtr hModule; } } } diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTX_FLAG.cs b/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTX_FLAG.cs index 554972b38b7..14216140502 100644 --- a/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTX_FLAG.cs +++ b/src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.ACTCTX_FLAG.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. @@ -11,14 +11,14 @@ internal partial class Kernel32 [Flags] public enum ACTCTX_FLAG : uint { - PROCESSOR_ARCHITECTURE_VALID = 0x00000001, - LANGID_VALID = 0x00000002, - ASSEMBLY_DIRECTORY_VALID = 0x00000004, - RESOURCE_NAME_VALID = 0x00000008, - SET_PROCESS_DEFAULT = 0x00000010, - APPLICATION_NAME_VALID = 0x00000020, - SOURCE_IS_ASSEMBLYREF = 0x00000040, - HMODULE_VALID = 0x00000080, + PROCESSOR_ARCHITECTURE_VALID = 0x00000001, + LANGID_VALID = 0x00000002, + ASSEMBLY_DIRECTORY_VALID = 0x00000004, + RESOURCE_NAME_VALID = 0x00000008, + SET_PROCESS_DEFAULT = 0x00000010, + APPLICATION_NAME_VALID = 0x00000020, + SOURCE_IS_ASSEMBLYREF = 0x00000040, + HMODULE_VALID = 0x00000080, } } } diff --git a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/ThemingScope.cs b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/ThemingScope.cs index 4581d330516..620fa4646f4 100644 --- a/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/ThemingScope.cs +++ b/src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/ThemingScope.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Runtime.InteropServices; +using System.IO; using static Interop; namespace System.Windows.Forms @@ -59,25 +59,68 @@ public static IntPtr Deactivate(IntPtr userCookie) return userCookie; } - public unsafe static bool CreateActivationContext(string dllPath, int nativeResourceManifestID) + public unsafe static bool CreateActivationContext(IntPtr module, int nativeResourceManifestID) { lock (typeof(ThemingScope)) { if (!s_contextCreationSucceeded) { - fixed (char* pDllPath = dllPath) + s_enableThemingActivationContext = new Kernel32.ACTCTXW + { + cbSize = (uint)sizeof(Kernel32.ACTCTXW), + lpResourceName = (IntPtr)nativeResourceManifestID, + dwFlags = Kernel32.ACTCTX_FLAG.HMODULE_VALID | Kernel32.ACTCTX_FLAG.RESOURCE_NAME_VALID, + hModule = module + }; + + s_hActCtx = Kernel32.CreateActCtxW(ref s_enableThemingActivationContext); + + s_contextCreationSucceeded = (s_hActCtx != new IntPtr(-1)); + } + + return s_contextCreationSucceeded; + } + } + + public unsafe static bool CreateActivationContext(Stream manifest) + { + lock (typeof(ThemingScope)) + { + if (!s_contextCreationSucceeded) + { + string tempFilePath = Path.Join(Path.GetTempPath(), Path.GetRandomFileName()); + using FileStream tempFileStream = new FileStream( + tempFilePath, + FileMode.CreateNew, + FileAccess.ReadWrite, + FileShare.Delete | FileShare.ReadWrite); + + manifest.CopyTo(tempFileStream); + + // CreateActCtxW gives a sharing violation if we have the handle open + tempFileStream.Close(); + + fixed (char* p = tempFilePath) { s_enableThemingActivationContext = new Kernel32.ACTCTXW { - cbSize = (uint)Marshal.SizeOf(), - lpSource = pDllPath, - lpResourceName = (IntPtr)nativeResourceManifestID, - dwFlags = Kernel32.ACTCTX_FLAG.RESOURCE_NAME_VALID + cbSize = (uint)sizeof(Kernel32.ACTCTXW), + lpSource = p }; s_hActCtx = Kernel32.CreateActCtxW(ref s_enableThemingActivationContext); } + s_contextCreationSucceeded = (s_hActCtx != new IntPtr(-1)); + + try + { + File.Delete(tempFilePath); + } + catch (Exception e) when (e is UnauthorizedAccessException or IOException) + { + // Don't want to take down WinForms if we can't delete is file + } } return s_contextCreationSucceeded; diff --git a/src/System.Windows.Forms/src/System.Windows.Forms.csproj b/src/System.Windows.Forms/src/System.Windows.Forms.csproj index 7758297018c..d1f5c323536 100644 --- a/src/System.Windows.Forms/src/System.Windows.Forms.csproj +++ b/src/System.Windows.Forms/src/System.Windows.Forms.csproj @@ -1,4 +1,4 @@ - + System.Windows.Forms @@ -13,6 +13,10 @@ true + + + + System.Windows.Forms.blank + + System.Windows.Forms.XPThemes.manifest + diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs index cd640417559..9e7e1b35065 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/Application.cs @@ -820,15 +820,27 @@ internal static void DoEventsModal() public static void EnableVisualStyles() { // Pull manifest from our resources - string assemblyLoc = typeof(Application).Assembly.Location; - if (assemblyLoc != null) - { - // CSC embeds DLL manifests as resource ID 2 - UseVisualStyles = ThemingScope.CreateActivationContext(assemblyLoc, nativeResourceManifestID: 2); - Debug.Assert(UseVisualStyles, "Enable Visual Styles failed"); + Module module = typeof(Application).Module; + IntPtr moduleHandle = Kernel32.GetModuleHandleW(module.Name); - s_comCtlSupportsVisualStylesInitialized = false; + if (moduleHandle != IntPtr.Zero) + { + // We have a native module, point to our native embedded manifest resource. + // CSC embeds DLL manifests as native resource ID 2 + UseVisualStyles = ThemingScope.CreateActivationContext(moduleHandle, nativeResourceManifestID: 2); } + else + { + // We couldn't grab the module handle, likely we're running from a single file package. + // Extract the manifest from managed resources. + using Stream stream = module.Assembly.GetManifestResourceStream( + "System.Windows.Forms.XPThemes.manifest"); + UseVisualStyles = ThemingScope.CreateActivationContext(stream); + } + + Debug.Assert(UseVisualStyles, "Enable Visual Styles failed"); + + s_comCtlSupportsVisualStylesInitialized = false; } /// diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs index 4233ebf135e..d691ece2a46 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/ApplicationTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Threading; using System.Windows.Forms.VisualStyles; using Microsoft.DotNet.RemoteExecutor; @@ -140,6 +141,15 @@ public void Application_VisualStyleState_Set_ReturnsExpected(VisualStyleState va }, valueParam.ToString()); } + [Fact] + public void Application_EnableVisualStyles_ManifestResourceExists() + { + // Check to make sure the manifest we use for single file publishing is present + using Stream stream = typeof(Application).Module.Assembly.GetManifestResourceStream( + "System.Windows.Forms.XPThemes.manifest"); + Assert.NotNull(stream); + } + private class CustomLCIDCultureInfo : CultureInfo { private readonly int _lcid;