Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Application.EnableVisualStyles in single file publishing mode #4149

Merged
merged 2 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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;
Expand All @@ -19,6 +20,7 @@ public unsafe struct ACTCTXW
public char* lpAssemblyDirectory;
public IntPtr lpResourceName;
public char* lpApplicationName;
public IntPtr hModule;
}
}
}
Original file line number Diff line number Diff line change
@@ -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.

Expand All @@ -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,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
JeremyKuhne marked this conversation as resolved.
Show resolved Hide resolved
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<Kernel32.ACTCTXW>(),
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;
Expand Down
9 changes: 8 additions & 1 deletion src/System.Windows.Forms/src/System.Windows.Forms.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AssemblyName>System.Windows.Forms</AssemblyName>
Expand All @@ -13,6 +13,10 @@
<UsePublicApiAnalyzers>true</UsePublicApiAnalyzers>
</PropertyGroup>

<ItemGroup>
<None Remove="Resources\System\Windows\Forms\XPThemes.manifest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\System.Windows.Forms.SourceGenerators\src\System.Windows.Forms.SourceGenerators.csproj"
ReferenceOutputAssembly="false"
Expand Down Expand Up @@ -634,6 +638,9 @@
<EmbeddedResource Include="Resources\System\Windows\Forms\ToolStrip\BlankToolstrip.ico">
<LogicalName>System.Windows.Forms.blank</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Resources\System\Windows\Forms\XPThemes.manifest">
<LogicalName>System.Windows.Forms.XPThemes.manifest</LogicalName>
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
Expand Down
26 changes: 19 additions & 7 deletions src/System.Windows.Forms/src/System/Windows/Forms/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down