Click here to Skip to main content
15,880,469 members
Articles / Desktop Programming / Win32
Tip/Trick

Protocol Association Enumerator

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
6 Oct 2015CPOL2 min read 17.9K   4   8
.NET classes to enumerate applications associated with protocol

Introduction

This tip should help somebody who may be faced with a problem to enumerate protocol or file extension associations. During development of an application, I was faced with a problem to enumerate all associations for a specific protocol. I searched through a lot of resources to find the right way. Most of them were about querying registry directly. It is doable but what if rules of protocol registration will change. So I started to dig MSDN and found a few interesting interfaces and methods. Unfortunately, the minimum supported system for the functionality is Windows Vista and 7, but if the restriction is ok for you, then the solution works. And so:

Just to enumerate applications associated with protocol, we need to call SHAssocEnumHandlersForProtocolByApplication, the method provides pointer to interface IEnumAssocHandlers, the same interface is using to enumerate all associations for file extension, the difference is a method which returns the enumeration interface for extension association, in the case we need to call SHAssocEnumHandlers method. So through the IEnumAssocHandlers interface, it is easy to retrieve all associations for protocol or file extensions registered at system. More details about associations how to register them and change or enumerate, you can find at MSDN.

At my example below, I provide set of .NET classes which wrap the Win32 API and COM interfaces.

First, what we need to do is to interop native methods and interfaces.

C#
#region Interop
#region IAssocHandlers && IEnumAssocHandlers
[Flags]
internal enum ASSOC_FILTER
{
    ASSOC_FILTER_NONE = 0,
    ASSOC_FILTER_RECOMMENDED = 0x1
};

[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("F04061AC-1659-4a3f-A954-775AA57FC083")]
internal 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")]
internal interface IEnumAssocHandlers
{
    int Next([In, MarshalAs(UnmanagedType.U4)] int celt,
             [Out, MarshalAs(UnmanagedType.Interface)] out IAssocHandler rgelt,
             [Out, MarshalAs(UnmanagedType.U4)] out int pceltFetched);
};
#endregion

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

    [DllImport("Shell32.dll", CharSet = CharSet.Auto)]
    internal static extern Int32 SHAssocEnumHandlersForProtocolByApplication(
        [In, MarshalAs(UnmanagedType.LPWStr)]
        string protocol,
        ref  Guid riid,
        [Out, MarshalAs(UnmanagedType.Interface)]
        out IEnumAssocHandlers ppEnumHandler);

    #region Helpers
    public static bool VerifyResult(int err)
    {
        if (err == 0)
        {
            return true;
        }
        return false;
    }
    public static void VerifyResultWithException(int err)
    {
        if (err != 0)
        {
            throw Marshal.GetExceptionForHR(err);
        }
    }
    #endregion
}
#endregion

The interfaces and Win32 methods are declared as internal. It is done to hide them under namespace and probably under library and not expose them to the consumer and provide only wrappers for the interface and methods, to have the opportunity to manage everything, initializing and releasing COM objects in wrappers.

Below are two interfaces which provide methods to initialize enumassoc object and its accessibility. Again the enumassoc COM object will be accessible only during current namespace.

C#
public interface IInitializer : IDisposable
{
    void Initialize(string protocolOrExt);
    void Reset();
}

internal interface IInstance
{
    IEnumAssocHandlers Get { get; }
}

Two classes implement the interfaces, first one encapsulates functionality to retrieve enumerations of protocol association, second one to retrieve enumerations of file extension.

C#
public sealed class ExtensionAssociations : IInitializer, IInstance
{
    #region Fields
    private string _protocolOrExt = string.Empty;
    private IEnumAssocHandlers _enumHandler = null;
    #endregion

    #region Constructor/Destructor
    public ExtensionAssociations()
    {
    }
    ~ExtensionAssociations()
    {
        Dispose();
    }
    #endregion

    #region IAssocEnumInitializer Members
    public void Initialize(string protocolOrExt)
    {
        _protocolOrExt = protocolOrExt;
    }
    public void Reset()
    {
        Dispose();
        Win32.VerifyResultWithException(Win32.SHAssocEnumHandlers
        (_protocolOrExt, ASSOC_FILTER.ASSOC_FILTER_NONE, out _enumHandler));
    }
    #endregion

    #region Internal members
    IEnumAssocHandlers IInstance.Get
    {
        get
        {
            return _enumHandler;
        }
    }
    #endregion

    #region IDisposable Members
    public void Dispose()
    {
        if (_enumHandler != null && Marshal.IsComObject(_enumHandler))
        {
            Marshal.ReleaseComObject(_enumHandler);
        }
        _enumHandler = null;
    }
    #endregion
}

public sealed class ProtocolAssociations : IInitializer, IInstance
{
    #region Fields
    private string _protocolOrExt = string.Empty;
    private static Guid _riid = new Guid("973810ae-9599-4b88-9e4d-6ee98c9552da");
    private IEnumAssocHandlers _enumHandler = null;
    #endregion

    #region Constructor/Destructor
    public ProtocolAssociations()
    {
    }
    ~ProtocolAssociations()
    {
        Dispose();
    }
    #endregion

    #region IAssocEnumInitializer Members
    public void Initialize(string protocolOrExt)
    {
        _protocolOrExt = protocolOrExt;
    }
    public void Reset()
    {
        Dispose();
        Win32.VerifyResultWithException
        (Win32.SHAssocEnumHandlersForProtocolByApplication(_protocolOrExt, ref _riid, out _enumHandler));
    }
    #endregion

    #region Internal members
    IEnumAssocHandlers IInstance.Get
    {
        get
        {
            return _enumHandler;
        }
    }
    #endregion

    #region IDisposable Members
    public void Dispose()
    {
        if (_enumHandler != null && Marshal.IsComObject(_enumHandler))
        {
            Marshal.ReleaseComObject(_enumHandler);
        }
        _enumHandler = null;
    }
    #endregion
}

The classes are type parameters of generic class who provide interface to enumerate and retrieve association of specific type.

C#
public class Associations<T> : IDisposable, IEnumerable<AssocHandler>, 
IEnumerator<AssocHandler> where T : IInitializer, new()
{
    #region Fields
    private T _initializer = new T();
    private AssocHandler _current = null;
    #endregion

    #region Constructor/Destructor
    public Associations(string protocolOrExt)
    {
        _initializer.Initialize(protocolOrExt);
        Reset();
    }
    ~Associations()
    {
        Dispose();
    }
    #endregion

    #region IDisposable Members
    public void Dispose()
    {
        _current = null;
        _initializer.Dispose();
    }
    #endregion

    #region IEnumerator<AssocHandler> Members
    public AssocHandler Current
    {
        get
        {
            return _current;
        }
    }
    #endregion

    #region IEnumerator Members
    object IEnumerator.Current
    {
        get
        {
            return _current;
        }
    }
    public bool MoveNext()
    {
        int outCelt = 0;

        IAssocHandler handler = null;
        _current = null;

        try
        {
            ((IInstance)_initializer).Get.Next(1, out handler, out outCelt);
            if (outCelt > 0)
            {
                _current = new AssocHandler(handler);
                return true;
            }
        }
        catch { }

        return false;
    }
    public void Reset()
    {
        _initializer.Reset();
    }
    #endregion

    #region IEnumerable<AssocHandler> Members
    public IEnumerator<AssocHandler> GetEnumerator()
    {
        Reset();
        return this;
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        Reset();
        return this;
    }
    #endregion
}

The class implements IEnumerable and IEnumerator interfaces and provide information only about registered protocols and extensions. And easily can be used at foreach statement for example.

Next wrapper encapsulates and provides access to properties and methods of association handler objects which implements IAssocHandler interface.

C#
public class AssocHandler : IDisposable
{
    #region Fields
    private IAssocHandler _handler = null;
    #endregion

    #region Constructor/Destructor
    protected AssocHandler()
    {
    }
    internal AssocHandler(IAssocHandler handler)
    {
        _handler = handler;
    }
    ~AssocHandler()
    {
        Dispose();
    }
    #endregion

    #region IDisposable Members
    public void Dispose()
    {
        if (_handler != null && Marshal.IsComObject(_handler))
        {
            Marshal.ReleaseComObject(_handler);
        }
        _handler = null;
    }
    #endregion

    #region IconInfo
    public class IconInfo
    {
        public string Path = string.Empty;
        public int Idx = -1;
    }
    #endregion

    #region Public methods
    public string GetName
    {
        get
        {
            string path = string.Empty;
            try {_handler.GetName(out path);}
            catch{}
            return path;
        }
    }
    public string GetUIName
    {
        get
        {
            string name = string.Empty;
            try { _handler.GetUIName(out name); }
            catch { }
            return name;
        }
    }
    public IconInfo GetIconLocation
    {
        get
        {
            IconInfo iconInfo = new IconInfo();
            try { _handler.GetIconLocation(out iconInfo.Path, out iconInfo.Idx); }
            catch { }
            return iconInfo;
        }
    }
    public bool IsRecommended
    {
        get
        {
            bool isRecommended = false;
            try {isRecommended = (_handler.IsRecommended() == 0);}
            catch{}
            return isRecommended;
        }
    }
    public bool MakeDefault(string pszDescription)
    {
        return Win32.VerifyResult(_handler.MakeDefault(pszDescription));
    }
    public void Invoke(object pdo)
    {
        Win32.VerifyResultWithException(_handler.Invoke(pdo));
    }
    public void CreateInvoker(object pdo, out object ppInvoker)
    {
        Win32.VerifyResultWithException(_handler.CreateInvoker(pdo, out ppInvoker));
    }
    #endregion
}

The interface of class copies interface of IAssocHandler, a detailed description of which you can find at MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/bb776320(v=vs.85).aspx

Example of Use

To use the classes, just create an instance of a generic class Associations<T> with specifying the actual type to substitute for the type parameter. Example below:

C#
Console.WriteLine(string.Format("{0} Protocol 'https' associations", Environment.NewLine));

int count = 1;
using (Associations<ProtocolAssociations> 
assocs = new Associations<ProtocolAssociations>("https"))
{
    foreach (AssocHandler handler in assocs)
    {
        Console.WriteLine(string.Format("{0}{4}. Assoc Name={1}, UIName={2}, Recommended={3}", 
                            Environment.NewLine, 
                            handler.GetName, 
                            handler.GetUIName, 
                            handler.IsRecommended, 
                            count));
        ++count;
    }
}

Console.WriteLine(string.Format
("{0} Extension '.html' associations", Environment.NewLine));

count = 1;
using (Associations<ExtensionAssociations> assocs = 
	new Associations<ExtensionAssociations>(".html"))
{
    foreach (AssocHandler handler in assocs)
    {
        Console.WriteLine(string.Format("{0}{4}. Assoc Name={1}, UIName={2}, Recommended={3}",
                            Environment.NewLine,
                            handler.GetName,
                            handler.GetUIName,
                            handler.IsRecommended,
                            count));
        ++count;
    }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Netgear Inc
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionWhich one is current? Pin
Member 131377746-Dec-19 3:03
Member 131377746-Dec-19 3:03 
PraiseAwesome Pin
Member 1223800526-May-19 8:50
Member 1223800526-May-19 8:50 
QuestionUnexpected Error 0x80004005 Pin
AnjumSKhan30-Dec-16 5:56
AnjumSKhan30-Dec-16 5:56 
AnswerRe: Unexpected Error 0x80004005 Pin
Maxim Komlev5-Jan-17 8:17
Maxim Komlev5-Jan-17 8:17 
if you could provide more details about the error, probably I could help. Thank you.
GeneralRe: Unexpected Error 0x80004005 Pin
AnjumSKhan5-Jan-17 16:57
AnjumSKhan5-Jan-17 16:57 
GeneralRe: Unexpected Error 0x80004005 Pin
Member 1335093915-Aug-17 18:34
Member 1335093915-Aug-17 18:34 
GeneralMy vote of 5 Pin
Farhad Reza15-Oct-15 21:05
Farhad Reza15-Oct-15 21:05 
GeneralRe: My vote of 5 Pin
Maxim Komlev16-Oct-15 5:05
Maxim Komlev16-Oct-15 5:05 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.