Search code examples
c#comcom-interoprcw

How to get the System.Type of a Runtime Callable Wrapper class from its CLSID?


Note: For background information please see this related question: How to get LINQPad to Dump() System.__ComObject references?

I am able to retrieve the CLSID of the RCW class corresponding to a COM object (obtained from another COM object, not initialized by my code) using IPersist.GetClassID().

Type.GetTypeFromCLSID() always returns the weakly-typed System.__ComObject, not the strongly-typed RCW class.

I need to get the System.Type of the strongly-typed RCW class to be able to wrap the COM object with it using Marshal.CreateWrapperOfType().

Is this possible or is this a non-starter due to how COM interop works?


Solution

  • Well here is what I ended up putting together as a proof of concept, just a handful of extension methods, really. This relies on the COM object implementing IPersist and having an RCW class in one of the PIAs loaded in the current AppDomain.

    internal static class ExtensionMethods
    {
        internal static object ConvertToRCW(this object o)
        {
            var guid = o.GetCLSID();
            if (guid != Guid.Empty)
            {
                return Marshal.CreateWrapperOfType(o, o.GetTypeFromGuid(guid));
            }
            else
            {
                return o;
            }
        }
    
        internal static Guid GetCLSID(this object o)
        {
            Guid guid = Guid.Empty;
            var p = o as IPersist;
            if (p != null)
                p.GetClassID(out guid);
            return guid;
        }
    
        internal static Type GetTypeFromGuid(this object o, Guid guid)
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                var types = assembly.GetLoadableTypes();
                foreach (var type in types)
                {
                    if (type.GUID == guid)
                        return type;
                }
            }
            return o.GetType();
        }
    
        internal static IEnumerable<Type> GetLoadableTypes(this Assembly assembly)
        {
            try
            {
                return assembly.GetTypes();
            }
            catch (ReflectionTypeLoadException e)
            {
                return e.Types.Where(t => t != null);
            }
        }
    }
    

    Used like this:

    var point = new ESRI.ArcGIS.Geometry.Point();
    point.PutCoords(1, 1);
    Console.WriteLine(point.GetType().FullName);
    Console.WriteLine(point.Envelope.GetType().FullName);
    Console.WriteLine(point.Envelope.ConvertToRCW().GetType().FullName);
    

    I get the following output:

    ESRI.ArcGIS.Geometry.PointClass
    System.__ComObject
    ESRI.ArcGIS.Geometry.EnvelopeClass

    Which was the desired result. Now to make this play nice with LINQPad (my original question).