How to get the System.Type of a Runtime Callable W

2019-05-07 04:21发布

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?

2条回答
别忘想泡老子
2楼-- · 2019-05-07 04:25

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

查看更多
Luminary・发光体
3楼-- · 2019-05-07 04:27

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.

查看更多
登录 后发表回答