如何让 LINQPad 转储() System.__ComObject 引用?

Posted

技术标签:

【中文标题】如何让 LINQPad 转储() System.__ComObject 引用?【英文标题】:How to get LINQPad to Dump() System.__ComObject references? 【发布时间】:2013-01-20 22:20:50 【问题描述】:

我正在尝试使用LINQPad 快速开发小型ArcObjects(ESRI's ArcGIS 软件的基于 COM 的库)应用程序,并且在使用它到 Dump() 的 COM 对象的属性方面取得了一些成功我从 .NET 初始化,但是从现有 COM 对象获得的任何 COM 对象都被简单地转储为 System.__ComObject 引用,这并不是特别有用:

This help topic 解释了为什么会发生这种情况,我想我理解这一点,但想知道有哪些选项可以解决此问题,尤其是在使 LINQPad(甚至)更强大的情况下。

有趣的是,Visual Studio 的调试器能够显示这些对象的属性,甚至是值类型的值:

Visual Studio 使用什么机制来实现这种自省,为什么 LINQPad 的 Dump 方法不做同样的事情? 编辑:请参阅有关 VS 如何做到这一点的相关问题:How does Visual Studio's debugger/interactive window dump the properties of COM Objects in .NET?

ArcObjects .NET SDK 包括带有 RCW 的 PIA,每个 CoClass 可以实现 COM 接口,因此我认为应该可以通过编程方式包装这些对象。

作为一种解决方法,当我碰巧知道应该使用哪个 CoClass 时,我已在我的 LINQ 查询中成功使用Marshal.CreateWrapperOfType() 来强制 LINQPad 转储对象的属性。当然,这只会正确转储值类型属性——任何基于 COM 的引用类型属性仍报告为System.__ComObject,因此正确的解决方案必须递归地工作以将这些属性也包装起来。

在previous question 中,我了解到如果 CoClass 实现了 IPersist,则可以在运行时确定 CoClass,ArcObjects 的很大一部分都是这样做的。我可以以某种方式使用这种技术或另一种技术来自动将System.__ComObject 从 PIA 强制转换为适当的 RCW 吗?如果是这样,我如何在 LINQPad 中实现它,例如通过提供ICustomMemberProvider 实现?是否可以将其设为递归,以便同时包装 COM 对象的属性?

我正在使用面向 .NET 4.0 的 LINQPad 4.x,但我也对支持 LINQPad 2.x 感兴趣(因此首选适用于 .NET 3.5 和 .NET 4.0 的解决方案,但这不是必需的) .

更新:我已经弄清楚了我的问题的第一部分,即如何使用 IPersist.GetClassID 返回的 CLSID 在其 RCW 中包装 System.__ComObject。请参阅此 related question 和 this answer 了解我正在使用的代码。

我仍然想知道如何将其用于 LINQPad 的 Dump 方法。

【问题讨论】:

我不知道 Visual Studio 是如何做到这一点的。如果你能弄清楚这一点,我可以让 LINQPad 做同样的事情。 谢谢乔,如果可以的话,那就太好了!我只能推测,但根据我有限的理解,它有两种基本的工作方式:1)反映支持接口的主互操作程序集和 2)使用IDispatch。由于我正在使用的所有对象都没有实现IDispatch,所以它必须是前者,至少对于调试视图的正常部分。我相信 VS2010 的“动态”视图使用IDispatch。但是,对于我的对象,动态视图只会显示“无法发现有关此对象的更多信息”。 在linked answer 中,我实际上比这两种方法更进一步,将 COM 对象显式包装在相应的 Runtime Callable Wrapper 中,从而产生最详细的输出(因为它包括成员来自所有实现的接口),但这依赖于实现IPersist的对象,这绝对不是通用的。您能否建议为我的 RCW 转换方法提供ICustomMemberProvider 是否可行? 是的,如果您实现 ICustomMemberProvider,您可以让 LINQPad 显示您喜欢的任何内容。虽然如果我能让 LINQPad 像 VS 一样自动处理所有 COM 对象会更好。 你不能为所有 System.__ComObjects 实现接口。仅适用于特定类型。 【参考方案1】:

我遇到了一些相同的问题(除了我正在使用 iTunes COM 库)。

在 Visual Studio 中,您没有意识到这一点,但每个调试窗口都要求 COM 库在您打开它时创建类型。这与 Dump() 不同,后者不是交互式的。

我找到的唯一解决方案是,如果我知道列表是什么类型,则将 OfType<>() 强制转换为该类型。这将遍历列表并强制 COM 创建元素。

所以在你上面的例子中你会说:

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

注意 - 您的数量可能会有所不同,事实证明对于 OP 示例来说这是必需的。

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

根据 COM,您可能需要将其带到下一步并从那里提取成员(对于 com,您必须要求获取值)如下所示:

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

如果有一种“神奇”的方式来实现这一点当然会很好,但我不相信这是因为 COM 的性质。

【讨论】:

这不适合我。 'ESRI.ArcGIS.Carto.ILayer' does not contain a definition for 'OfType' and the best extension method overload 'System.Linq.Queryable.OfType&lt;TResult&gt;(System.Linq.IQueryable)' has some invalid arguments Instance argument: cannot convert from 'ESRI.ArcGIS.Carto.ILayer' to 'System.Linq.IQueryable' 另外,如果我理解正确,OfType 不会强制转换,它只是按类型过滤。使用 Cast 本身并没有帮助,因为您不能将 System.__ComObject 转换为其层次结构之外的类,只能转换为接口,即使这样仍然只会导致 System.__ComObject 被转储。 最后,让 LINQPad 转储对象的“神奇”方法是使用 Marshal.CreateWrapperOfType() 用适当的 RCW 显式包装它。我的问题更多是关于以通用的、LINQPad 友好的方式自动化这个过程,因为这个库有数千种类型。大部分库实现 IPersist 的事实可能对这项工作很有用,这就是我提到它的原因。 此语法按预期编译和过滤,但仍转储System.__ComObject:var layers = map.EnumerateLayers().OfType&lt;IGeoFeatureLayer&gt;().Dump(); @blah238 - 是的,这就是为什么我在第二部分中谈到使用 select 和 new。

以上是关于如何让 LINQPad 转储() System.__ComObject 引用?的主要内容,如果未能解决你的问题,请参考以下文章

如何让android打印掉崩溃系统应用程序的核心转储?

在LINQPad中使用FreeSql查询数据库

如何查看友好的 .NET 调用堆栈?

如何在 LinqPad 中提交更改

我如何让铿锵声转储没有颜色的 AST?

如何让 JUnit 测试(从 Ant 脚本驱动)转储导致失败的异常堆栈?