Исключение EntryPointNotFoundException при использовании TaskDialog в C#
-
06-07-2019 - |
Вопрос
Я использую следующий код для вызова TaskDialog.
[DllImport("ComCtl32", CharSet = CharSet.Unicode, PreserveSig = false)]
internal static extern void TaskDialogIndirect(
[In] ref TASKDIALOGCONFIG pTaskConfig,
[Out] out int pnButton,
[Out] out int pnRadioButton,
[Out] out bool pfVerificationFlagChecked);
Однако я получаю исключение "Не удается найти точку входа с именем 'TaskDialogIndirect' в DLL 'ComCtl32'".
Я взял этот код.Я использую Windows 7 x64 (RC).
Что я делаю не так?
Решение
Ничего, кроме того, что это функция Vista
Обновить:Эта проблема была связана с параллельными сборками:эти функции присутствуют только в comctl32.dll версии 6, но по соображениям совместимости Vista загрузит более раннюю версию, если вы не укажете иное.Подход, которого придерживается большинство людей (включая меня), заключается в использовании манифеста.Это оказалось сложным и, возможно, в любом случае не является правильным решением, особенно если то, что вы пишете, является библиотекой:вы не обязательно хотите заставлять все приложение использовать общие элементы управления 6.
Правильное решение состоит в том, чтобы подтолкнуть новая активация контекст при вызове одного из API-интерфейсов только для Vista.Контекст активации будет использовать правильную версию comctl32.dll, оставляя остальную часть приложения в покое, и манифест не требуется.
К счастью, это легко сделать.Некоторый полный код, который уже существует База знаний MS Knowledge Base.Код из статьи (KB 830033) выполняет трюк как есть.
Альтернативный управляемый API:Полную оболочку для TaskDialog Vista и TaskDialogIndirect можно найти здесь:
http://code.msdn.microsoft.com/WindowsAPICodePack
Для WPF используйте следующее:
Загрузите "Библиотеку примеров VistaBridge" с сайта http://code.msdn.microsoft.com/VistaBridge после загрузки откройте проект, а затем соберите его (если вы хотите просмотреть весь код, изучите файлы в папках \Library или \Interop).Теперь вы можете взять библиотеку DLL из VistaBridge\bin\debug\ и добавить ссылку на нее в свой проект, также вы должны добавить инструкцию using для каждого из различных модулей VistaBridge.Например:
использование Microsoft.SDK.Samples.VistaBridge.Interop, или .Library, или .Properties, или .Services - в зависимости от ваших потребностей.
Проект VistaBridge включает API для многих других функций Vista (таких как TaskDialog, диалоговые окна Vista OpenFile и SaveFile и, конечно же, эффекты Aero Glass). чтобы опробовать их, запустите проект VistaBridge.
Другие советы
Для использования диалога задач требуется 6-я версия библиотеки общих элементов управления Windows (ComCtl32.dll)! Из соображений совместимости приложения не привязываются к этой версии по умолчанию. Один из способов привязки к версии 6 - поместить файл манифеста рядом с исполняемым файлом (с именем YourAppName.exe.manifest) со следующим содержимым:
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
Этот манифест также может быть встроен как ресурс Win32 в ваш исполняемый файл (с именем RT_MANIFEST и ID, установленным в 1), если вы не хотите иметь дополнительный автономный файл. Visual Studio может выполнить эту работу за вас, если вы свяжете свой файл манифеста в свойствах вашего проекта.
Основываясь на ответе almog.ori (который получил несколько осиротевших ссылок), я внес небольшое изменение в связанный код, я ломал голову над несколькими днями:
База знаний MS помогла ( Архив ), полный код с усыновлениями, сделанными мной:
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;
}
}
}
Затем я использовал это так:
using (new EnableThemingInScope(true))
{
// The call all this mucking about is here for.
VistaUnsafeNativeMethods.TaskDialogIndirect(ref config, out result, out radioButtonResult, out verificationFlagChecked);
}
в TaskDialogInterop.cs
, предоставленной в Оболочке диалогового окна задач WPF на GitHub р>
Для получения дополнительной информации о возможных SEHException
в финализаторе EnableThemingInScope
см. этот Вопрос по SO