Search code examples
c#winapicommarshallingshell32

Use SHAssocEnumHandlers in C#


I am struggling to call the WinAPI SHAssocEnumHandlers in 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();
    }
  }
}

When calling the SHAssocEnumHandlers i only get a

"Unexpected error: HRESULT: 0x80004005 (E_FAIL)"

The stacktrace shows the exeption at

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

I think I maybe missing some implementation. But I cannot figure out what.

Update 1

This error only occur on Windows 7. On an Windows 10 machine it works fine. (Tested on various win7 and win10 machines)


Solution

  • I found a way to get the result on Windows 7 maschine.

    In this way you go over pointers.

    I am not 100% sure why it works. And i am sure it is not the safest way.

    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();
        }
      }
    }
    

    Some explanation:

    + 3 * sizeof(int)
    

    The interfaces IEnumAssocHandlers and IAssocHandler inherits from IUnknown. So they always implement three methods. That's why you have to move the pointer to your first function by 3. And + 4 for your second function and so on. Not sure if this explanation is correct, but it works :)

    IntPtr[] pArrayAssocHandlers  = new IntPtr[255];
    

    The Next funtion returns an Array of IAssocHandlers. I found no way to find out the number of IAssocHandler for a given extension. So i set the size of the array to 255.

    The Next function get three arguments (in NumberOfElementsToRetrieve, out ArrayOfIAssocHandlers, out NumberOfRetrievedElements) Sound a bit weird but thats whats in the documentation.

    Relevant Links:
    SHAssocEnumHandlers
    IEnumAssocHandlers
    IAssocHandler

    Note:
    All kinds of error handling is missing in this example.