How to get LINQPad to Dump() System.__ComObject references?
Asked Answered
M

1

6

I am playing around with using LINQPad to rapidly develop small ArcObjects (a COM-based library for ESRI's ArcGIS software) applications and have had some success in using it to Dump() the properties of COM objects that I initialize from .NET, but any COM objects that are obtained from an existing COM object are simply dumped as System.__ComObject references, which is not particularly useful:

LINQPad Screenshot

This help topic explains why this is happening, which I think I understand, but would like to know what options there are for working around this behavior, especially in the context of making LINQPad (even) more powerful.

Interestingly, Visual Studio's debugger is able to display the properties of these objects, and even values for value types:

Visual Studio debugging ArcObjects

What mechanism does Visual Studio use to achieve this introspection, and why doesn't LINQPad's Dump method do the same? Edit: See related question about how VS does this: How does Visual Studio's debugger/interactive window dump the properties of COM Objects in .NET?

The ArcObjects .NET SDK includes PIAs with RCWs for each CoClass a COM interface may be implemented by, so I'm thinking it should be possible to wrap these objects programmatically.

As a workaround I have successfully used Marshal.CreateWrapperOfType() within my LINQ queries to coerce LINQPad to dump the properties of the object when I happen to know which CoClass should be used. Of course, this only properly dumps value type properties -- any COM-based reference type properties are still reported as System.__ComObject, so a proper solution would have to work recursively to get those wrapped as well.

In a previous question I learned that the CoClass can be determined at runtime if it implements IPersist, which a good portion of ArcObjects do. Can I somehow use this technique, or another one, to automatically coerce a System.__ComObject to the appropriate RCW from the PIAs? And if so, how can I implement this in LINQPad, e.g. by providing an ICustomMemberProvider implementation? Can this be made to be recursive, so that properties that are also COM objects be wrapped as well?

I am using LINQPad 4.x which targets .NET 4.0, but am also interested in supporting LINQPad 2.x (so solutions that work on both .NET 3.5 and .NET 4.0 would be preferred, but that's not a requirement).

Update: I've figured out the first part of my question which was how to wrap a System.__ComObject in its RCW using the CLSID returned by IPersist.GetClassID. See this related question and this answer for the code I'm using.

I would still like to know how I can work this into LINQPad's Dump method.

Montanez answered 5/2, 2013 at 21:5 Comment(8)
I don't know how Visual Studio does this. If you can figure this out, I can make LINQPad do the same.Vesicle
Thanks Joe, that would be really great if you could! I can only speculate, but from my limited understanding there are two basic ways it could be working 1) Reflecting on the Primary Interop Assemblies for supported interfaces and 2) Using IDispatch. Since none of the objects I'm working with implement IDispatch it has to be the former, at least for the normal part of the debug view. I believe in VS2010's "Dynamic" view it uses IDispatch. With my objects, though, Dynamic view only says "No further information on this object could be discovered".Montanez
In the linked answer, I am actually going a step further than either of these approaches by explicitly wrapping the COM Objects in their appropriate Runtime Callable Wrapper, which produces the most detailed output (because it includes members from all implemented interfaces), but this relies on the objects implementing IPersist, which is definitely not universal. Could you suggest whether providing an ICustomMemberProvider for my RCW conversion approach is feasible?Montanez
Yes, if you implement ICustomMemberProvider you can make LINQPad display whatever you like. Although it would be better if I could make LINQPad work like VS automatically with all COM objects.Vesicle
Agreed that would be best. I wonder though, how can I implement ICustomMemberProvider in a way that it works on all System.__ComObjects? Or is it only possible to to use on user-defined types?Montanez
You can't implement the interface for all System.__ComObjects. Only for specific types.Vesicle
Ah, that's what I feared. Thanks.Montanez
I have asked a related question about how VS does this: https://mcmap.net/q/1110655/-how-does-visual-studio-39-s-debugger-interactive-window-dump-the-properties-of-com-objects-in-net/386205Montanez
I
0

I've been having some of the same issues (except I'm working with iTunes COM library).

In visual studio you don't realize it but each debug window is asking the COM library to create the type when you open it. This is different than Dump() which is, well, not interactive.

The only solution I've found is if I know what type the list is to do a OfType<>() cast to that type. This will iterate over the list and force COM to create the elements.

So in your example above you would say:

var layers = map.EnumerateLayers("etc")
      .Select(s => s.OfType<Layer>())
      .Dump();

NB - Your millage may vary, it turns out for the OP example this was needed.

var layers = map.EnumerateLayers()
      .OfType<IGeoFeatureLayer>()
      .Dump();

Depending on the COM you might have to take this to the next step and pull out the members from there (with com you have to ask to get the value) something like this:

var layers = map.EnumerateLayers("etc")
      .Select(x => x.OfType<Layer>())
      .Select(x => new { x.Depth, x.Dimention, }) // etc 
      .Dump();

Sure would be nice if there was a "magical" way to make this happen, but I don't believe there is because of the nature of COM.

Inventory answered 6/2, 2013 at 19:27 Comment(6)
This does not compile for me. 'ESRI.ArcGIS.Carto.ILayer' does not contain a definition for 'OfType' and the best extension method overload 'System.Linq.Queryable.OfType<TResult>(System.Linq.IQueryable)' has some invalid arguments Instance argument: cannot convert from 'ESRI.ArcGIS.Carto.ILayer' to 'System.Linq.IQueryable'Montanez
Also if I understand correctly, OfType doesn't cast, it just filters by type. Using Cast itself does not help because you cannot cast System.__ComObject to a class outside of its hierarchy, only an interface, and even then that still just results in System.__ComObject being Dumped.Montanez
Lastly, the "magical" way of getting LINQPad to Dump the object is to wrap it explicitly with the appropriate RCW using Marshal.CreateWrapperOfType(). My question is more about automating this process in a generic, LINQPad-friendly way as this library has thousands of types. The fact that a large portion of the library implements IPersist could be useful to this endeavor, which is why I mentioned it.Montanez
This syntax compiles and filters as expected, but still dumps System.__ComObject: var layers = map.EnumerateLayers().OfType<IGeoFeatureLayer>().Dump();Montanez
@Montanez - yes that is why I had the 2nd part where I talk about using select and new.Inventory
Yes that is pretty much where I started and the whole point of my question is to avoid having to do it that way.Montanez

© 2022 - 2024 — McMap. All rights reserved.