wpf: Calls to comctl32.dll succeed in .NET 4.8, but fail in .NET 5 with System.EntryPointNotFoundException

  • .NET Core Version: 5.0.100 and 3.1.404
  • Windows version: Windows 10 (18363.1139)
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: No
  • Is this bug related specifically to tooling in Visual Studio (e.g. XAML Designer, Code editing, etc…)? No

Problem description:

Calls to comctl32.dll succeed in .NET Framework 4.8, but fail in .NET 5 and also in .NET Core 3.1 with a System.EntryPointNotFoundException.

e.g.

[DllImport("comctl32.dll", PreserveSig = false)]
private static extern void TaskDialogIndirect([In] ref TaskDialogConfig pTaskConfig, out int pnButton,
    out int pnRadioButton, [MarshalAs(UnmanagedType.Bool)] out bool pfVerificationFlagChecked);

// ...

var config = new TaskDialogConfig
{
    pszWindowTitle = "Task dialog title",
    pszMainInstruction = "Task dialog main instruction",
    dwCommonButtons = TaskDialogCommonButtonFlags.OkButton | TaskDialogCommonButtonFlags.CancelButton,
    // ...
};

using (new ComCtlv6ActivationContext(enable: true))
{
    TaskDialogIndirect(ref config, out _, out _, out _);
}

Actual behavior:

image

System.EntryPointNotFoundException: Unable to find an entry point named 'TaskDialogIndirect' in DLL 'comctl32.dll'.
   at TaskDialogApi.TaskDialog.TaskDialogIndirect(TaskDialogConfig& pTaskConfig, Int32& pnButton, Int32& pnRadioButton, Boolean& pfVerificationFlagChecked)

Expected behavior:

Display the Task Dialog requested

image

Minimal repro:

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 1
  • Comments: 28 (23 by maintainers)

Most upvoted comments

I have no idea who added the manifest in .NET Framework, I just want to clarify that the fact that it lives next to mscorlib is probably not very telling. There are more than 400 files next to mscorlib in .NET Framework…

That said I think this is something which should ideally work “seamlessly” whenever “Windows Dektop” is asked for. For example, it might make sense to automatically include this in the application manifest for any app which targets net5-windows (or net6-windows).

Well - it’s already in runtime repo right now 😉 Depending on the design it might not belong there. For example if we were to implement the solution where windows apps get different manifest by default, it would probably belong to the SDK repo.

But I think there’s a discussion to have:

  • We want to preserve backward compat with .NET Framework -> we would have to include XPTheme.manifest in the shared framework (next to coreclr.dll and other “root” assemblies). This doesn’t feel right to be honest, since we would have to do this for all apps, not just net5-windows apps - any app on windows regardless of its purpose would get that file.
  • We want to fix the experience for new apps -> we can tie it to the TFM and make SDK use different default manifest based on the TFM (net5 -> no change, net5-windows -> the new manifest) - but there are probably other similar solutions.

@vatsan-madhavan I’m just saying that the affected domain is (.NET apps that want to use ComCtl32) - (WinForms apps). I make no claims as to whether that set is equal to (WPF apps). Even if that were true today, there’s no guarantee it will be true in the future. Thus it’s prudent to treat this as a “non-WinForms” problem rather than as a WPF problem, and solve it at a deeper level than WPF.

Also, it’s possible that other cases will arise of .NET apps that want to use some technology unknown to WPF or WinForms or any specific .NET subframework/component, requiring a manifest or similar app-level enabler. It shouldn’t be WPF’s job to solve these cases. Set the right precedent now.

@RussKie I think that guys here want XPThemes.manifest to be delivered separately. For now, wpf completely haven’t it and in winforms it’s embedded in winforms.dll.

The app tries to load the required manifest itself, here. This code looks for a file named “XPThemes.manifest” next to mscorlib.dll, whose location it determines by typeof(object).Assembly.Location. You’re seeing three cases:

  1. In .NET Framework, the manifest is found (in “C:\Windows\Microsoft.NET\Framework\v4.0.30319\XPThemes.manifest”), and the P/Invoke into comctl32.dll succeeds.
  2. In .NET Core + WinForms, the manifest is not found, but WinForms has loaded an equivalent one in initialization. The P/Invoke succeeds.
  3. In .NET Core + WPF, the manifest is not found. The P/Invoke fails with EntryPointNotFoundException. [I get the same failure in .NET Framework by changing the name to “XPThemes.manifestXXX”.]

This is an app bug, not WPF. The app assumes something that is true in .NET Framework but not in .NET Core, namely that “XPThemes.manifest” lives next mscorlib.dll. Whether that assumption should hold is a question for .NET runtime; WPF has no stake, and no reason to load an equivalent manifest the way WinForms does.

The workaround is going to be the same, but the underlying issue is probably very different. dotnet/runtime#42593 is about WinForms which has code to enable comctl32 APIs on app start. WPF doesn’t seem to have that code. I don’t know why it works on .NET Framework - the application doesn’t have the needed section in its manifest there either. Somebody from WPF should look into this and determine how it worked in .NET Framework and how/if should work in .NET Core.

WinForms does carry the manifest in .NET Core, but it’s a resource in the winforms dll. You can see how WinForms solved a problem around single-file, by extracting the that resource to disk and loading it as a manifest - https://github.com/dotnet/winforms/pull/4149/files#diff-4bb832ea247560f6b393cd7989713d833469e8b70c47961b086a88b5345439aeR834-R838.

In .NET Framework, XPThemes.manifest was supplied by either WinForms or the runtime in a location adjacent to mscorlib.dll.

In .NET Core, WindowsDesktop framework forgot to provide XPThemes.manifest adjacent to mscorlib.dll - this is a compat break between .NET Framework and .NET Core.

Whether WinForms or runtime makes it available again is a good question to discuss and resolve. I recommend moving this issue to winforms rather than just closing it.

/cc @dotnet/dotnet-winforms

@SamBent will look at the manifest loading for .NET Framework to see why this works there. Thanks.