c# - 我怎么知道 `ThisWorkbook` 是 `Workbook` ?

标签 c# com vbe vbide

我正在使用 VBIDE API,不能假定主机应用程序是 Excel 或任何 Office 应用程序。

所以我只知道我正在查看一个 VBComponent,它的 Typevbext_ct_document

在 VBE 的直接 Pane 中,我可以获得以下输出:

?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1"))
VBComponent
?TypeName(Sheet1)
Worksheet

但是 Sheet1 对象只存在于运行时环境中,所以如果我是 C# 插件,我什至看不到它。

唯一接近我需要的东西是通过组件的 ParentNext 属性:

?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1").Properties("Parent").Object)
Workbook
?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1").Properties("Next").Object)
Worksheet

这让我得到了我想要的类型名称......但是在错误的组件上!对于顶级文档对象 ThisWorkbook,我将 Application 对象作为 Parent:

?TypeName(Application.VBE.ActiveVBProject.VBComponents("ThisWorkbook").Properties("Parent").Object)
Application

该方法可能有用,但只有当我硬编码特定于主机的逻辑时它知道无论哪个组件都具有类型为“Application”的“Parent”属性是一个 Workbook 实例,当主机应用程序是 Excel 时...并且不能保证其他主机中的其他文档模块甚至具有该“父”属性,所以我很困惑。

我对字面意义上的任何都持开放态度 - 从 p/invoke 调用和低级 COM“反射”魔术(ITypeInfo 魔术)到 ...到...我不知道 - 不安全 代码带有时髦的指针,需要 //here be dragons 评论 - 任何 lead 可以欢迎可能最终找到可行的解决方案。


据我所知,VBE 加载项与宿主存在于同一进程中,因此某处 有一个指向 ThisWorkbookSheet1 以及其他任何内容的指针VBA 项目中的其他文档类型 VBComponent

?ObjPtr(ThisWorkbook)
 161150920

我想我只需要以某种方式捕获那个指针,我就会到达我需要去的地方。

最佳答案

不幸的是,vbComponent Properties 集合的值/对象只是 CoClass 实例值的反射(reflect),因此它们在所有 VBA 主机中都不可靠。例如,您不知道 Parent 属性将存在于 Properties 集合中。

当主机支持文档类型组件时,由主机定义文档支持的接口(interface)的 GUID。宿主通常还负责创建/删除实际文档,就像只有 Excel 对象模型可以将工作表添加到工作簿,而 VBIDE 不能。

您已经谈到了工作簿和工作表,所以我将两者都包括在内......

不幸的是,VBIDE 隐藏了文档型组件的一些细节,它在导出模块时故意省略了这些细节,甚至将导出的文档型模块转换为类模块文本,就像这样Worksheet 调用了 Sheet1,因此它不能作为文档类型模块重新导入:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Sheet1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Sub Foo()

End Sub

将上面的内容与实际存储在 Sheet1 模块中(以压缩格式)的文档模块文本进行比较:

Attribute VB_Name = "Sheet1"
Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True
Sub Foo()

End Sub

请注意 真实 模块文本中存在的 3 个附加属性:

Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}"
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True

根据 OleViewer,GUID 0{00020820-0000-0000-C000-000000000046} 与 CoClass Worksheet 完全匹配:

[
  uuid(00020820-0000-0000-C000-000000000046),
  helpcontext(0x0002a410)
]
coclass Worksheet {
    [default] interface _Worksheet;
    [default, source] dispinterface DocEvents;
};

工作簿模块也会出现相同的行为。这是 VBIDE 导出的文本:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ThisWorkbook"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True

以及来自 VBA 二进制文件中 IStream 的原始文本:

Attribute VB_Name = "ThisWorkbook"
Attribute VB_Base = "0{00020819-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True

这一次,正如预期的那样,GUID 0{00020819-0000-0000-C000-000000000046} 是一个工作簿 CoClass:

[
  uuid(00020819-0000-0000-C000-000000000046),
  helpcontext(0x000305b8)
]
coclass Workbook {
    [default] interface _Workbook;
    [default, source] dispinterface WorkbookEvents;
};

以上内容很值得了解,但它并不能解决您的问题,除非您可以获得组件的内存中 IStream 的句柄,我认为您做不到。如果您可以从上次保存的主机文档版本中加载详细信息,那么您可以从基础文档中加载详细信息,但我认为您不需要那样做,并且它可以最终特定于主机(考虑 Access 在表中存储 VBA 的方式。)

但是,VBIDE 确实为您提供了有关 CoClass 的线索。 vbComponent 的属性集合返回 CoClass 中存在的确切 个属性,如果您检查这些属性的名称、参数和类型,您会发现它们完全 匹配相应 CoClass 的成员,一直到它们在 CoClass 定义中出现的顺序。

例如,Worksheet vbComponent 的前 10 个属性是:

Application
Creator
Parent
CodeName
_CodeName
Index
Name
Next
OnDoubleClick
OnSheetActivate

以及来自 CoClass Worksheet 中调度接口(interface) _Worksheet 的相应 propget(和 propput)条目(方法已删除):

    [id(0x00000094), propget, helpcontext(0x0002a411)]
    Application* Application();
    [id(0x00000095), propget, helpcontext(0x0002a412)]
    XlCreator Creator();
    [id(0x00000096), propget, helpcontext(0x0002a413)]
    IDispatch* Parent();
    [id(0x0000055d), propget, helpcontext(0x0002a7fc)]
    BSTR CodeName();
    [id(0x80010000), propget, helpcontext(0x0002a7fd)]
    BSTR _CodeName();
    [id(0x80010000), propput, helpcontext(0x0002a7fd)]
    void _CodeName([in] BSTR rhs);
    [id(0x000001e6), propget, helpcontext(0x0002a7fe)]
    long Index();
    [id(0x0000006e), propget, helpcontext(0x0002a800)]
    BSTR Name();
    [id(0x0000006e), propput, helpcontext(0x0002a800)]
    void Name([in] BSTR rhs);
    [id(0x000001f6), propget, helpcontext(0x0002a801)]
    IDispatch* Next();
    [id(0x00000274), propget, hidden, helpcontext(0x0002a802)]
    BSTR OnDoubleClick();
    [id(0x00000274), propput, hidden, helpcontext(0x0002a802)]
    void OnDoubleClick([in] BSTR rhs);
    [id(0x00000407), propget, hidden, helpcontext(0x0002a803)]
    BSTR OnSheetActivate();

如果您可以反射(reflect)主机类型库的 CoClasses 并散列属性 names(也许只使用 propget 名称),那么您可以将散列与 VBIDE 组件中名称的散列进行比较。Properties收藏。

这是一种获取类型的迂回方式,但如果无法访问 IStream,我认为这将是您唯一的方式。

关于c# - 我怎么知道 `ThisWorkbook` 是 `Workbook` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37078992/

相关文章:

vba - 为什么 VBA 编辑器会打开 Outlook?

c# - 如何迭代 MS-Access 表单上的控件?

c# - 强类型数据集与弱类型数据集

c++ - MIDL。为什么回调 C++ 接口(interface)在方法添加到 idl 后不更新?

c# - 使用 C++ 中的 COM 对象,在 C#.NET 中返回对象 []

c - SHV ID : 79856 Can not create validator

c# - 反序列化其他列表中的列表时,Newtonsoft 反序列化重复元素

C# - 清理事件处理程序

c# - Azure Blob 403 禁止使用 SAS 创建和使用 BlobContainer

vba - 用户窗体设计时间 : Move controls with arrowkeys