Используйте SHAssocEnumHandlers в C#

Я изо всех сил, чтобы вызвать WinAPI SHAssocEnumHandlers в C#.

using System;
using System.Runtime.InteropServices;

namespace AssocHandlerTest
{
  [Flags]
  public enum ASSOC_FILTER
  {
    ASSOC_FILTER_NONE = 0x0,
    ASSOC_FILTER_RECOMMENDED = 0x1
  };

  [ComImport]
  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  [Guid("F04061AC-1659-4a3f-A954-775AA57FC083")]
  public interface IAssocHandler
  {
    int GetName([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppsz);
    int GetUIName([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppsz);
    int GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszPath, [Out] out int pIndex);
    int IsRecommended();
    int MakeDefault([In, MarshalAs(UnmanagedType.LPWStr)] string pszDescription);
    int Invoke([In, MarshalAs(UnmanagedType.IUnknown)] object pdo);
    int CreateInvoker([In, MarshalAs(UnmanagedType.IUnknown)] object pdo, [Out, MarshalAs(UnmanagedType.IUnknown)] out object ppInvoker);
  };

  [ComImport]
  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  [Guid("973810ae-9599-4b88-9e4d-6ee98c9552da")]
  public interface IEnumAssocHandlers
  {
    int Next([In, MarshalAs(UnmanagedType.U4)] int celt, [Out, MarshalAs(UnmanagedType.Interface)] out IAssocHandler rgelt, [Out, MarshalAs(UnmanagedType.U4)] out int pceltFetched);
  };

  class Program
  {
    [DllImport("Shell32.dll", CharSet = CharSet.Auto)]
    static extern bool SHAssocEnumHandlers(
    [In, MarshalAs(UnmanagedType.LPWStr)] string pszExtra, [In] ASSOC_FILTER afFilter, [Out, MarshalAs(UnmanagedType.Interface)] out IEnumAssocHandlers ppEnumHandler);

    static void Main(string[] args)
    {
      const string extension = ".html";
      try
      {
        IEnumAssocHandlers enumAssocHandlers = null;
        SHAssocEnumHandlers(extension, ASSOC_FILTER.ASSOC_FILTER_NONE, out enumAssocHandlers);
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
      Console.ReadLine();
    }
  }
}

При вызове SHAssocEnumHandlers я получаю только

"Неожиданная ошибка: HRESULT: 0x80004005 (E_FAIL)"

Stacktrace показывает исключение в

System.StubHelpers.InterfaceMarshaler.ConvertToManaged(IntPtr pUnk, IntPtr itfMT, IntPtr classMT, Int32 flags)

Я думаю, что я, возможно, пропускаю некоторую реализацию. Но я не могу понять, что.

Обновление 1

Эта ошибка возникает только в Windows 7. На компьютере с Windows 10 она работает нормально. (Проверено на различных машинах win7 и win10)

1 ответ

Решение

Я нашел способ получить результат на Windows 7.

Таким образом, вы идете по указателям.

Я не уверен на 100%, почему это работает. И я уверен, что это не самый безопасный способ.

using System;
using System.Runtime.InteropServices;

namespace AssocHandlerWithPointer
{
  [Flags]
  public enum ASSOC_FILTER
  {
    ASSOC_FILTER_NONE = 0x00000000,
    ASSOC_FILTER_RECOMMENDED = 0x00000001
  }

  public class Test
  {
    [DllImport("Shell32", EntryPoint = "SHAssocEnumHandlers", PreserveSig = false)]
    public extern static void SHAssocEnumHandlers([MarshalAs(UnmanagedType.LPWStr)] string pszExtra, ASSOC_FILTER afFilter, [Out] out IntPtr ppEnumHandler);

    // IEnumAssocHandlers
    [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
    private delegate int FuncNext(IntPtr refer, int celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface, SizeParamIndex = 1)] IntPtr[] rgelt, [Out] out int pceltFetched);

    // IAssocHandler
    [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
    private delegate int FuncGetName(IntPtr refer, out IntPtr ppsz);

    [UnmanagedFunctionPointer(CallingConvention.Winapi, CharSet = CharSet.Unicode)]
    private delegate int FuncGetUiName(IntPtr refer, out IntPtr ppsz);  


    static void Main(string[] args)
    {
      const string extension = ".html";

      IntPtr pEnumAssocHandlers;
      SHAssocEnumHandlers(extension, ASSOC_FILTER.ASSOC_FILTER_RECOMMENDED, out pEnumAssocHandlers);

      IntPtr pFuncNext = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pEnumAssocHandlers) + 3 * sizeof(int));
      FuncNext next = (FuncNext)Marshal.GetDelegateForFunctionPointer(pFuncNext, typeof(FuncNext));

      IntPtr[] pArrayAssocHandlers  = new IntPtr[255];
      int num;

      int resNext = next(pEnumAssocHandlers, 255, pArrayAssocHandlers, out num);
      if (resNext == 0)
      {
        for (int i = 0; i < num; i++)
        {
          IntPtr pAssocHandler = pArrayAssocHandlers[i];
          IntPtr pFuncGetName = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pAssocHandler) + 3 * sizeof(int));
          FuncGetName getName = (FuncGetName)Marshal.GetDelegateForFunctionPointer(pFuncGetName, typeof(FuncGetName));
          IntPtr pName;
          int resGetName = getName(pAssocHandler, out pName);
          Console.WriteLine("Path: " + Marshal.PtrToStringUni(pName));

          IntPtr pFuncGetUiName = Marshal.ReadIntPtr(Marshal.ReadIntPtr(pAssocHandler) + 4 * sizeof(int));
          FuncGetUiName getUiName = (FuncGetUiName)Marshal.GetDelegateForFunctionPointer(pFuncGetUiName, typeof(FuncGetUiName));
          IntPtr pUiName;
          int resGetUiName = getUiName(pAssocHandler, out pUiName);
          Console.WriteLine("UIName: " + Marshal.PtrToStringUni(pUiName));

          Marshal.Release(pArrayAssocHandlers[i]);
        }
      }
      Marshal.Release(pEnumAssocHandlers);
      Console.ReadLine();
    }
  }
}

Некоторое объяснение:

+ 3 * sizeof(int)

Интерфейсы IEnumAssocHandlers и IAssocHandler наследуются от IUnknown. Поэтому они всегда реализуют три метода. Вот почему вы должны переместить указатель на вашу первую функцию на 3. И + 4 для вашей второй функции и так далее. Не уверен, что это объяснение верно, но оно работает:)

IntPtr[] pArrayAssocHandlers  = new IntPtr[255];

Функция Next возвращает массив IAssocHandlers. Я не нашел способа узнать номер IAssocHandler для данного расширения. Поэтому я установил размер массива 255.

Функция Next получает три аргумента (в NumberOfElementsToRetrieve, в ArrayOfIAssocHandlers, в NumberOfRetrievedElements). Звучит немного странно, но об этом говорится в документации.

Соответствующие ссылки:
SHAssocEnumHandlers
IEnumAssocHandlers
IAssocHandler

Замечания:
В этом примере отсутствуют все виды обработки ошибок.

Другие вопросы по тегам