I'm using the following code to call a TaskDialog.
[DllImport("ComCtl32", CharSet = CharSet.Unicode, PreserveSig = false)]
internal static extern void TaskDialogIndire开发者_运维技巧ct(
[In] ref TASKDIALOGCONFIG pTaskConfig,
[Out] out int pnButton,
[Out] out int pnRadioButton,
[Out] out bool pfVerificationFlagChecked);
However, I get the exception "Unable to find an entry point named 'TaskDialogIndirect' in DLL 'ComCtl32'."
I took this code. I am using Windows 7 x64 (RC).
What am I doing wrong?
Nothing except this is a vista feature
UPDATE: This probem had to do with side by side assemblies: these functions are present only in comctl32.dll version 6, but, for compatibility reasons, Vista will load an earlier version unless you tell it otherwise. The approach most people (including me) have been taking is to use a manifest. This has proven to be tricky, and may not be the right solution anyway, especially if what you're writing is a library: you don't necessarily want to force the entire application to use common controls 6.
The right solution is to push a new activation context when calling one of the Vista-only APIs. The activation context will use the correct version of comctl32.dll while leaving the rest of the application alone, and no manifest is required.
Fortunately, this is easy to do.Some complete code that already exists MS Knowledgebase. The code from the article (KB 830033) does the trick as is.
Alternative Managed API: A full wrapper for Vista's TaskDialog & TaskDialogIndirect can be found here:
http://code.msdn.microsoft.com/WindowsAPICodePack
For WPF use the following:
Download the 'VistaBridge Sample Library' from http://code.msdn.microsoft.com/VistaBridge once downloaded, open the project and then build it (if you want to look through all the code, examine the files in the \Library or \Interop folders). You can now take the DLL from VistaBridge\bin\debug\ and add a reference to it in your project, as well you must add a using statement for each of the different VistaBridge modules. For Example:
using Microsoft.SDK.Samples.VistaBridge.Interop or .Library or .Properties or .Services - Depending on your needs.
The VistaBridge project includes API's for many other Vista Features (such as the TaskDialog, Vista OpenFile and SaveFile Dialogs, and of course the Aero Glass Effects) to try these out, run the VistaBridge Project.
The use of Task Dialog requires version 6 of the Windows Common Controls DLL(ComCtl32.dll)! For compatibility reasons, applications don’t bind to this version by default. One way to bind to version 6 is to place a manifest file alongside your executable (named YourAppName.exe.manifest), with the following content:
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
This manifest can also be embedded as a Win32 resource inside your executable (with the name RT_MANIFEST and ID set to 1), if you don’t want to have the extra standalone file. Visual Studio can do this work for you, if you associate your manifest file in your project’s properties.
Based on almog.ori's answer (which got some orphaned links) I made a small change to the linked code, I puzzled around several days:
MS Knowledgebase helped (Archiv), Full Code with adoptions made by me:
using System.Runtime.InteropServices;
using System;
using System.Security;
using System.Security.Permissions;
using System.Collections;
using System.IO;
using System.Text;
namespace MyOfficeNetAddin
{
/// <devdoc>
/// This class is intended to use with the C# 'using' statement in
/// to activate an activation context for turning on visual theming at
/// the beginning of a scope, and have it automatically deactivated
/// when the scope is exited.
/// </devdoc>
[SuppressUnmanagedCodeSecurity]
internal class EnableThemingInScope : IDisposable
{
// Private data
private IntPtr cookie; // changed cookie from uint to IntPtr
private static ACTCTX enableThemingActivationContext;
private static IntPtr hActCtx;
private static bool contextCreationSucceeded = false;
public EnableThemingInScope(bool enable)
{
if (enable)
{
if (EnsureActivateContextCreated())
{
if (!ActivateActCtx(hActCtx, out cookie))
{
// Be sure cookie always zero if activation failed
cookie = IntPtr.Zero;
}
}
}
}
// Finalizer removed, that could cause Exceptions
// ~EnableThemingInScope()
// {
// Dispose(false);
// }
void IDisposable.Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (cookie != IntPtr.Zero)
{
if (DeactivateActCtx(0, cookie))
{
// deactivation succeeded...
cookie = IntPtr.Zero;
}
}
}
private bool EnsureActivateContextCreated()
{
lock (typeof(EnableThemingInScope))
{
if (!contextCreationSucceeded)
{
// Pull manifest from the .NET Framework install
// directory
string assemblyLoc = null;
FileIOPermission fiop = new FileIOPermission(PermissionState.None);
fiop.AllFiles = FileIOPermissionAccess.PathDiscovery;
fiop.Assert();
try
{
assemblyLoc = typeof(Object).Assembly.Location;
}
finally
{
CodeAccessPermission.RevertAssert();
}
string manifestLoc = null;
string installDir = null;
if (assemblyLoc != null)
{
installDir = Path.GetDirectoryName(assemblyLoc);
const string manifestName = "XPThemes.manifest";
manifestLoc = Path.Combine(installDir, manifestName);
}
if (manifestLoc != null && installDir != null)
{
enableThemingActivationContext = new ACTCTX();
enableThemingActivationContext.cbSize = Marshal.SizeOf(typeof(ACTCTX));
enableThemingActivationContext.lpSource = manifestLoc;
// Set the lpAssemblyDirectory to the install
// directory to prevent Win32 Side by Side from
// looking for comctl32 in the application
// directory, which could cause a bogus dll to be
// placed there and open a security hole.
enableThemingActivationContext.lpAssemblyDirectory = installDir;
enableThemingActivationContext.dwFlags = ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
// Note this will fail gracefully if file specified
// by manifestLoc doesn't exist.
hActCtx = CreateActCtx(ref enableThemingActivationContext);
contextCreationSucceeded = (hActCtx != new IntPtr(-1));
}
}
// If we return false, we'll try again on the next call into
// EnsureActivateContextCreated(), which is fine.
return contextCreationSucceeded;
}
}
// All the pinvoke goo...
[DllImport("Kernel32.dll")]
private extern static IntPtr CreateActCtx(ref ACTCTX actctx);
// changed from uint to IntPtr according to
// https://www.pinvoke.net/default.aspx/kernel32.ActiveActCtx
[DllImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ActivateActCtx(IntPtr hActCtx, out IntPtr lpCookie);
// changed from uint to IntPtr according to
// https://www.pinvoke.net/default.aspx/kernel32.DeactivateActCtx
[DllImport("Kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeactivateActCtx(int dwFlags, IntPtr lpCookie);
private const int ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x004;
private struct ACTCTX
{
public int cbSize;
public uint dwFlags;
public string lpSource;
public ushort wProcessorArchitecture;
public ushort wLangId;
public string lpAssemblyDirectory;
public string lpResourceName;
public string lpApplicationName;
}
}
}
I then used it that way:
using (new EnableThemingInScope(true))
{
// The call all this mucking about is here for.
VistaUnsafeNativeMethods.TaskDialogIndirect(ref config, out result, out radioButtonResult, out verificationFlagChecked);
}
in TaskDialogInterop.cs
provided in WPF Task Dialog Wrapper on GitHub
For more info on possible SEHException
s in the Finalizer of EnableThemingInScope
see this Question on SO
精彩评论