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

2

4

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?

Disreputable answered 7/2, 2013 at 17:6 Comment(5)
The second parameter passed to CreateWrapperOfType needs to be defined in the .NET space (with ComImport attributes, etc.), so it won't work without defining this type somehow (using C# code, tlbimp, or Reflection Emit)Eyeleen
I have PIAs with the RCWs defined (see related question for more info).Disreputable
Yes, that doesn't change the answer :-) it's still your job to provide the type, as there can be an infinite set of types in .NET space that define the corresponding COM object. So, if you have PIAs, you could browse all the classes in the PIAs namespace (using Reflection for example), build a Dictionary<Guid, Type>, and provide the type from this dictionary when you have the guid.Eyeleen
I figured as much, that was what I had started working on before I thought there had to be a better way. The GetTypeFromCLSID function got my hopes up and then dashed them away when I read that it always returns System.__ComObject :p This may not be workable given how large the object libraries I'm working with are.Disreputable
This question looks relevant: Get type from GUIDDisreputable
D
2

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).

Disreputable answered 8/2, 2013 at 3:32 Comment(1)
where do I get the IPersist definition from?Dicephalous
E
1

Type.GetTypeFromCLSID() just returns a dynamic COM wrapper.

Strongly-typed RCW must be defined in the .NET space. It must be classes that are decorated with the ComImportAttribute. .NET can't create these classes automatically ex-hihilo. They are defined manually (in .NET code), or by PIAs, or by tlbimp, or by Reflection Emit mechanism for example, like all .NET types. There is no preset relation between a COM CLSID and .NET corresponding classes for the reason there may be multiple .NET classes corresponding to the same CLSID.

If you have these types available, what you could do is scan a defined set of namespaces and build a Dictionary<Guid, Type> from it for example.

Eyeleen answered 7/2, 2013 at 20:44 Comment(3)
I just wanted to say this is useful, and thanks. I think in my case the CLSID is guaranteed to be uniquely tied to a particular strongly-typed RCW -- but I can only obtain the CLSID in the first place if the COM object implements IPersist (a worthwhile percentage of the library I'm working with does, otherwise I wouldn't bother). It would be nice if there was a predefined mechanism for this, though.Disreputable
What you can also check is if the COM object implements IProvideClassInfo: msdn.microsoft.com/fr-fr/library/windows/desktop/…. From there you can get a ITypeInfo, and from there the type GUID: #4652369, but you'll have to define all these interfaces in your code :-)Eyeleen
Yeah unfortunately none of them implement IProvideClassInfo, and only a handful implement IDispatch.Disreputable

© 2022 - 2024 — McMap. All rights reserved.