VB6/VBA 遍历所有预先声明的 class 对象

VB6/VBA Iterate through all predeclared class objects

我可以动态迭代所有预先声明的对象吗?

这是我已经处理了一段时间的问题。理想情况下,我会遍历所有 类 并检查它们是否实现了某些接口。如果他们这样做,则对他们执行一些代码。

目前我必须提供一些 类 的数组来执行,例如:

ClassesToCheck = Array(Task_Class1,Task_Class2,Task_Class3,Task_Class4, ...)

Dim klass as object
For each klass in ClassesToCheck
  if klass.implements(ITask) then
    Call klass.execute()
  end if
next

在理想的世界里,我会做这样的事情:

Dim klass as object
For each klass in GET_PREDECLARED_CLASS_OBJECTS_FROM_MEMORY()
  if klass.implements(ITask) then
    Call klass.execute()
  end if
next

我不认为有任何简单的方法可以做到这一点,但我已经对 VBA 运行时内存做了一些 research/exploration...我认为这应该是可能的并且已经使用 VBA6.DLL 找到了一些 VB6 示例,但是,不幸的是,Microsoft Office 本机未提供此 DLL。但是,VBA6.DLL 可能是 'compiled into' Microsoft Office 本身。所以 methods/fields 也应该保存在内存中的某个地方,你只需要知道他们在哪里使用指针数学(这是我的理论)

我想没有人有这方面的经验吧?

我不知道如何利用 tlbinf32.dll,直接从内存中获取 class 信息,但是,如果您坚持使用当前方法,但使用对象集合而不是对象数组,您可以执行以下操作:

Private ConditionalExecution()

    Dim PredeclaredClasses As Collection

    Set PredeclaredClasses = New Collection

        PredeclaredClasses.Add Task_Class1
        PredeclaredClasses.Add Task_Class2
        PredeclaredClasses.Add Task_Class3
        PredeclaredClasses.Add Task_Class4

    ExecuteIfImplementsInterface PredeclaredClasses

End Sub 

Private Sub ExecuteIfImplementsInterface(ByVal Classes As Collection)

    Dim klass as object

    For each klass in classes
        If TypeOf klass Is ITask Then klass.execute()
    next

End Sub 

当然,这将取决于这些 classes 必须通过 "dimming" 作为它们正在实现的接口实例化的事实,如下所示:

Dim bar As ITask

Set bar = New Task_Class1

旁注:如果您想知道,是的,我不擅长想出方法名称。

VB_PredeclaredId 属性使您的 Class1 标识符自动引用该名称的全局范围对象,例如UserForm1 是 class 模块的名称(一个有设计器,但那部分是无关紧要的), 它是生成的全局自动魔法对象的名称通过 VBA 在运行时,编译器知道 Class1.DoStuff 是合法的 因为 它知道 Class1 已将 VB_PredeclaredId 设置为 True.

感谢 Wayne Phillips (vbWatchDog) 和其他贡献者的工作和贡献,Rubberduck taps into this internal API

如链接代码 (C#) 所示,通过将指针转换为结构,您可以从其 References 集合中获取 VBA 项目的 ITypeLib使用此特定布局:

[StructLayout(LayoutKind.Sequential)]
internal struct VBEReferencesObj
{
    IntPtr _vTable1;     // _References vtable
    IntPtr _vTable2;
    IntPtr _vTable3;
    IntPtr _object1;
    IntPtr _object2;
    public IntPtr _typeLib; // <--- here's the pointer you want
    IntPtr _placeholder1;
    IntPtr _placeholder2;
    IntPtr _refCount;
}

在 VBA 中,这将是用户定义的 Type,可能如下所示:

Public Type VBEReferencesObj
    vTable1 As LongPtr
    vTable2 As LongPtr
    vTable3 As LongPtr
    object1 As LongPtr
    object2 As LongPtr
    typelibPointer As LongPtr '<~ only this one matters
    placeholder1 As LongPtr
    placeholder2 As LongPtr
    refCount As LongPtr
End Type

获得指向 ITypeLib 的指针后,您应该能够获得 VBA 项目的类型库。

从那里,您需要迭代类型,并从那里确定类型的 TYPEFLAGS 是否启用了 TYPEFLAG_PREDECLID(我们这样做 here)。

显然这是很多极易崩溃的试错编码,我不建议这样做,但无论如何,它是可能,如果不建议的话。

随时研究 Rubberduck.VBEEditor.ComManagement.TypeLibs 命名空间。