为什么在使用 COM 时 TypeName() 从 .GetType 和 TypeOf 返回不同的结果?

CBR*_*F23 2 vb.net com types windows-shell typename

我觉得我会从了解这些功能如何工作的差异中受益匪浅,这样我就可以更好地了解何时使用每个功能。

我在使用两种不同的互操作(Excel 和 EPDM)时遇到了非常困难的时间,它们都广泛使用了弱类型参数。我一直在使用返回的对象并将它们转换为正确的类型时遇到问题(根据文档)。在浪费了大量时间之后,我发现对COM 对象使用TypeNameGetTypeTypeOf 运算符会产生不同的结果,并且在不同的情况下,每一个都可能比下一个更可靠或更不可靠。

现在,在大多数情况下TypeName(),用 COM 对象确定类型似乎是最可靠的。然而,完全避免其他两个函数对我来说似乎是一种货物崇拜,除此之外,今天我遇到了一个有趣的问题,我似乎无法将对象强制转换为TypeName(). 在对该问题的评论中提出了一个有趣的概念,即实现的对象IDispatch实际上可能返回调度的接口类型名,这可以部分解释差异。

我真的很想更好地了解这些函数实际上是如何工作的,但是我在.NET ReferenceSource中迷失了方向,所以我在这个问题上提供了一个赏金,希望有人能解释这些不同的函数是如何工作的以及在什么应该使用每个上下文。

以下是使用 Excel 互操作的代码摘录。

Dim DocProps As Object 
DocProps = WeeklyReports.CustomDocumentProperties 'WeeklyReports is a Workbook object
Debug.Print(DocProps Is Nothing)
Debug.Print(TypeName(DocProps))
Debug.Print(TypeOf (DocProps) Is DocumentProperties)
Debug.Print(DocProps.GetType.ToString)
Run Code Online (Sandbox Code Playgroud)

输出是:

虚假的
DocumentProperties
虚假的
System.__ComObject

Han*_*ant 5

这是一个很长的故事,有点怀疑英语是否会削减它。它确实需要了解 COM 的工作原理以及它如何集成到 Office 产品中。

以极快的速度,COM在其核心是一个非常重要的基于接口的编程范式。接口很简单,类很难。您还可以在 .NET 设计中看到一些东西,一个类只能从一个基类派生,但可以实现任意数量的接口。为了使语言互操作顺利进行,重要的是尽可能减少对语言实现细节的依赖。

在任何现代语言中,您都习惯使用 COM 没有做的很多事情。它不支持异常,只支持错误代码。根本没有泛型的概念。没有反射。不支持方法重载。不支持任何实现继承,类的概念是完全隐藏的。它只显示为一个数字,CLSID,一个标识类类型的 guid。在创建类对象的 COM 组件中实现了工厂函数。COM 组件保留该对象的所有权。然后客户端代码只使用接口来调用使用方法和获取或设置属性。 CoCreateInstance()是执行此操作的主要运行时支持函数。

这被进一步缩减为称为 OLE 自动化的子集,这是您与 Office 互操作时使用的风格。它严格限制了可用于属性和方法参数的类型,并以规定的方式处理诸如字符串和数组之类的困难类型。它确实添加了一些功能,它支持通过 IDispatch 接口进行后期绑定,这对脚本语言很重要。还有 VARIANT,一种可以存储任意类型的值或对象引用的数据类型。并支持类型库,即 COM 服务器实现的接口的机器可读描述。.NET 元数据是完全类似的。

对于这个问题很重要,它将类可以实现的接口数量限制为一个。对于完全不支持接口概念的语言(如 VBA、Javascript 和 VBScript 等脚本语言以及早期的 Visual Basic 版本)很重要。Office 互操作对象模型的设计充分考虑了这些限制。

所以从使用这种语言来自动化Office程序的程序员的角度来看,完全看不出他的语言运行时实际上是在使用接口。他在他的程序中所看到和使用的只是看起来像类名的标识符,而不是接口名。这DocumentProperties实际上是一个接口名是你可以在对象浏览器看到的。只需在搜索框中键入名称,它就会在右下面板正确注释“公共接口 DocumentProperties / Microsoft.Office.Core 的成员”。

Office 对象模型的一个特定细节在这里非常重要,许多属性和方法返回类型都是 VARIANT。可以存储任意值或对象引用的 OLE 自动化类型,当您使用 .NET 时,它会映射到 System.Object。该Workbook.CustomDocumentProperties性质就是这样。即使该属性被记录为实际返回 DocumentProperties 接口引用。他们这样做可能是为了留出余地,以便有一天返回另一种界面。“自定义文档属性”相当必要。

在支持动态类型的语言中,属性是 VARIANT 并不重要,他们会大步前进。然而,在强类型语言中这是非常痛苦的。并且对支持自动完成的编程编辑器非常不友好,例如 VS 的 IntelliSense。您通常所做的是将变量声明为预期的接口类型:

  Dim DocProps As DocumentProperties
  DocProps = CType(WeeklyReports.CustomDocumentProperties, Microsoft.Office.Core.DocumentProperties)
Run Code Online (Sandbox Code Playgroud)

现在一切都亮了。如果您喜欢在 Option Strict Off 生效的情况下对 VB.NET 进行编程,则您也不需要 CType() 类型转换。这使它成为一种很好地支持动态类型的编程语言。


我们快到了。只要您声明 DocProps,Object那么编译器就知道有关该接口的 bean。调试器也没有,它不受变量声明的帮助,只能看到它是__System.ComObject来自运行时类型的。所以它不是什么都没有,这很容易理解,属性 getter 没有失败并且文档具有属性。

TypeName()函数使用 IDispatch 接口的一个特性,它在运行时公开类型信息。这恰好适用于您的情况,通常不会,该函数首先调用IDispatch::GetTypeInfo()以获取 ITypeInfo 接口引用,然后调用ITypeLib::GetDocumentation()。那行得通,你会得到接口名称。否则与 .NET 中的反射相当,只是没有那么强大。不要过分依赖它,有很多 COM 组件没有实现这一点。

对您的问题至关重要的TypeOf (DocProps) Is DocumentProperties是失败的鲸鱼。当您尝试编写我之前提出的代码时,您会发现一些东西。你会得到一个讨厌的运行时异常 System.InvalidCastException:

{“无法将‘System.__ComObject’类型的 COM 对象转换为接口类型‘Microsoft.Office.Core.DocumentProperties’。此操作失败,因为 COM 组件上的 QueryInterface 调用 IID 为‘{2DF8D04D-5BFA-101B’ -BDE5-00AA0044DE52}' 由于以下错误而失败:不支持此类接口(来自 HRESULT 的异常:0x80004002 (E_NOINTERFACE))。"}

换句话说,Excel 文档在骗你。您会返回一个类似于DocumentProperties的接口,它仍然具有该接口记录的成员,但不再与 Microsoft.Office.Core.DocumentProperties 相同。它可能曾经是,很多个月以前。这篇知识库文章中隐藏了一个令人讨厌的小细节:

注意 DocumentProperties 和 DocumentProperty 接口是后期绑定接口。要使用这些接口,您必须像对待 IDispatch 接口一样对待它们。