我怎么知道 `ThisWorkbook` 是 `Workbook`?
How do I know that `ThisWorkbook` is a `Workbook`?
我正在使用 VBIDE API,不能假设主机应用程序是 Excel,或者任何 Office 应用程序。
所以我只知道我在看 VBComponent
,它的 Type
是 vbext_ct_document
。
在 VBE 的直接窗格中,我可以获得以下输出:
?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1"))
VBComponent
?TypeName(Sheet1)
Worksheet
但是 Sheet1
对象仅存在于运行时环境中,所以如果我是 C# 加载项,我什至看不到它。
唯一接近我需要的东西是通过组件的 Parent
和 Next
属性:
?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
该方法可能有用,但仅当我硬编码特定于主机的逻辑时知道哪个组件具有"Parent" 属性 类型 "Application" 是一个 Workbook
实例,当主机应用程序是 Excel... 并且不能保证其他主机中的其他文档模块甚至有 "Parent" 属性,所以我很困惑。
我对 任何东西 都持开放态度 - 来自 p/invoke 调用和低级 COM "reflection" 魔法(ITypeInfo
那种magic) to ... to... 我不知道 - unsafe
带有时髦指针的代码需要 // here be dragons
评论 - 任何 lead 可能欢迎最终找到一个可行的解决方案。
据我所知,VBE 加载项与主机位于同一进程中,因此 某处 有指向 ThisWorkbook
和 Sheet1
以及其他任何内容的指针VBA 项目中的文档类型 VBComponent
。
?ObjPtr(ThisWorkbook)
161150920
我想我只需要抓住那个指针以某种方式,我就会到达我需要去的地方。
不幸的是,vbComponent 属性 values/objects collection 只是 CoClass 实例值的反映,因此它们在所有 VBA 主机上都不可靠。例如,您无法知道 Parent
属性 将存在于属性 collection.
中
当主机支持 document-type-components 时,由主机定义文档支持的界面的 GUID。宿主通常也会对 creating/removing 实际文档负责,就像只有 Excel object 模型可以将 sheet 添加到工作簿,而 VBIDE 不能。
您已经谈到了工作簿和工作sheets,所以我将两者都包括在内....
不幸的是,VBIDE隐藏了一些关于document-type组件的细节,它在导出模块时故意省略了这些细节,甚至将导出的document-type模块转换为class模块文本,例如 Worksheet
称为 Sheet1
,因此它 不能 作为 document-type 模块重新导入:
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
模块中实际存储(以压缩格式)的 document-module 文本进行比较:
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
注意 real 模块文本中存在的 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;
};
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
以及 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;
};
以上内容很值得了解,但它不能解决您的问题,除非您可以获得组件的 in-memory IStream 的句柄,我认为您做不到。如果您可以从上次保存的主机文档版本中加载详细信息,那么您可以从基础文档中加载详细信息,但我认为您不需要那样,and 它可能最终成为 host-specific(考虑 Access 在 table 中存储 VBA 的方式。)
但是,VBIDE 确实 为您提供了有关 CoClass 的线索。 vbComponent 的属性 collection returns CoClass 中存在的属性 确切 数量,如果您检查这些属性的名称、参数和类型,您会发现它们完全匹配相应 CoClass 的成员,一直到它们在 CoClass 定义中出现的顺序。
例如,Worksheet vbComponent 的前 10 个属性是:
Application
Creator
Parent
CodeName
_CodeName
Index
Name
Next
OnDoubleClick
OnSheetActivate
以及 CoClass Worksheet 中调度接口 _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();
如果您可以反映主机类型库的 CoClasses 并散列 属性 names(也许只使用 propget 名称),那么您可以将散列与VBIDE component.Properties collection.
中的名称
这是获取类型的一种 round-about 方式,但无法访问 IStream,我 认为 这将是你唯一的方式。
我正在使用 VBIDE API,不能假设主机应用程序是 Excel,或者任何 Office 应用程序。
所以我只知道我在看 VBComponent
,它的 Type
是 vbext_ct_document
。
在 VBE 的直接窗格中,我可以获得以下输出:
?TypeName(Application.VBE.ActiveVBProject.VBComponents("Sheet1"))
VBComponent
?TypeName(Sheet1)
Worksheet
但是 Sheet1
对象仅存在于运行时环境中,所以如果我是 C# 加载项,我什至看不到它。
唯一接近我需要的东西是通过组件的 Parent
和 Next
属性:
?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
该方法可能有用,但仅当我硬编码特定于主机的逻辑时知道哪个组件具有"Parent" 属性 类型 "Application" 是一个 Workbook
实例,当主机应用程序是 Excel... 并且不能保证其他主机中的其他文档模块甚至有 "Parent" 属性,所以我很困惑。
我对 任何东西 都持开放态度 - 来自 p/invoke 调用和低级 COM "reflection" 魔法(ITypeInfo
那种magic) to ... to... 我不知道 - unsafe
带有时髦指针的代码需要 // here be dragons
评论 - 任何 lead 可能欢迎最终找到一个可行的解决方案。
据我所知,VBE 加载项与主机位于同一进程中,因此 某处 有指向 ThisWorkbook
和 Sheet1
以及其他任何内容的指针VBA 项目中的文档类型 VBComponent
。
?ObjPtr(ThisWorkbook)
161150920
我想我只需要抓住那个指针以某种方式,我就会到达我需要去的地方。
不幸的是,vbComponent 属性 values/objects collection 只是 CoClass 实例值的反映,因此它们在所有 VBA 主机上都不可靠。例如,您无法知道 Parent
属性 将存在于属性 collection.
当主机支持 document-type-components 时,由主机定义文档支持的界面的 GUID。宿主通常也会对 creating/removing 实际文档负责,就像只有 Excel object 模型可以将 sheet 添加到工作簿,而 VBIDE 不能。
您已经谈到了工作簿和工作sheets,所以我将两者都包括在内....
不幸的是,VBIDE隐藏了一些关于document-type组件的细节,它在导出模块时故意省略了这些细节,甚至将导出的document-type模块转换为class模块文本,例如 Worksheet
称为 Sheet1
,因此它 不能 作为 document-type 模块重新导入:
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
模块中实际存储(以压缩格式)的 document-module 文本进行比较:
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
注意 real 模块文本中存在的 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;
};
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
以及 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;
};
以上内容很值得了解,但它不能解决您的问题,除非您可以获得组件的 in-memory IStream 的句柄,我认为您做不到。如果您可以从上次保存的主机文档版本中加载详细信息,那么您可以从基础文档中加载详细信息,但我认为您不需要那样,and 它可能最终成为 host-specific(考虑 Access 在 table 中存储 VBA 的方式。)
但是,VBIDE 确实 为您提供了有关 CoClass 的线索。 vbComponent 的属性 collection returns CoClass 中存在的属性 确切 数量,如果您检查这些属性的名称、参数和类型,您会发现它们完全匹配相应 CoClass 的成员,一直到它们在 CoClass 定义中出现的顺序。
例如,Worksheet vbComponent 的前 10 个属性是:
Application
Creator
Parent
CodeName
_CodeName
Index
Name
Next
OnDoubleClick
OnSheetActivate
以及 CoClass Worksheet 中调度接口 _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();
如果您可以反映主机类型库的 CoClasses 并散列 属性 names(也许只使用 propget 名称),那么您可以将散列与VBIDE component.Properties collection.
中的名称这是获取类型的一种 round-about 方式,但无法访问 IStream,我 认为 这将是你唯一的方式。