我怎么知道`ThisWorkbook`是`工作簿'?

Mat*_*don 8 c# com vbe vbide

我正在使用VBIDE API,并且不能假设主机应用程序是Excel或任何Office应用程序.

所以我所知道的是,我正在看着它VBComponent,而且它Typevbext_ct_document.

在VBE的即时窗格中,我可以获得此输出:

?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1"))
VBComponent
?TypeName(Sheet1)
Worksheet
Run Code Online (Sandbox Code Playgroud)

但是该Sheet1对象仅存在于运行时环境中,所以如果我是C#加载项,我甚至都看不到它.

那得到任何接近我所需要的,唯一的一点是通过ParentNext组件的属性:

?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1").Properties("Parent").Object)
Workbook
?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1").Properties("Next").Object)
Worksheet
Run Code Online (Sandbox Code Playgroud)

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

?TypeName(Application.VBE.ActiveVBProject.VBComponents("ThisWorkbook").Properties("Parent").Object)
Application
Run Code Online (Sandbox Code Playgroud)

这种方法可能很有用,但前提是硬编码特定于主机的逻辑,该逻辑知道哪个组件具有"应用程序"类型的"父"属性是Workbook主机应用程序是Excel时的实例...并且不能保证其他主机中的其他文档模块甚至会有"父"属性,所以我非常难过.

几乎任何东西都是开放的- 从p/invoke调用和低级COM"反射"魔术(ITypeInfo魔法)到...到......我不知道 - unsafe带有时髦指针的代码需要// here be dragons注释-任何导线,可以潜在地结束了一个可行的解决方案是值得欢迎的.


据我所知的VBE加载生活在同一个进程中的主机,这样的地方有一个指针ThisWorkbook,并Sheet1和任何其他文件类型的VBComponentVBA项目.

?ObjPtr(ThisWorkbook)
 161150920
Run Code Online (Sandbox Code Playgroud)

我想我只需要以某种方式抓住指针,我将成为我需要的地方.

Thu*_*ame 7

遗憾的是,vbComponent Properties集合的值/对象只是CoClass实例值的反映,因此它们在所有VBA主机中都不可靠.例如,你可以不知道Parent属性将属性集合中存在.

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

你已经谈到了工作簿和工作表,所以我将包括两个....

不幸的是,VBIDE隐瞒一些关于文档类型组件的详细信息,并导出模块时,它故意忽略这些细节,甚至远销文档类型的模块转换为类模块的文本,这样WorksheetSheet1,所以它不能被重新导入为文档类型模块:

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
Run Code Online (Sandbox Code Playgroud)

将上述内容与模块内实际存储的文档模块文本(压缩格式)进行比较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
Run Code Online (Sandbox Code Playgroud)

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

Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}"
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True
Run Code Online (Sandbox Code Playgroud)

根据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;
};
Run Code Online (Sandbox Code Playgroud)

Workbook模块发生相同的行为.这是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
Run Code Online (Sandbox Code Playgroud)

来自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
Run Code Online (Sandbox Code Playgroud)

这次,正如预期的那样,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;
};
Run Code Online (Sandbox Code Playgroud)

以上是很好的知识,但它并没有解决你的问题,除非你可以得到组件的内存IStreams的句柄,我认为你不能.如果您可以从上次保存的主机文档版本加载详细信息,那么您可以从基础文档加载详细信息,但我认为您不希望这样,并且它最终可能是特定于主机的(考虑Access将VBA存储在表中的方式.)

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

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

Application
Creator
Parent
CodeName
_CodeName
Index
Name
Next
OnDoubleClick
OnSheetActivate
Run Code Online (Sandbox Code Playgroud)

以及来自CoClass工作表中的dispinterface _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();
Run Code Online (Sandbox Code Playgroud)

如果您可以反映主机类型库的CoClasses并散列属性名称(可能只是使用propget名称),那么您可以将散列与VBIDE的component.Properties集合中的名称进行比较.

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