Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VB6/VBA Iterate through all predeclared class objects

Can I dynamically iterate over all Predeclared objects?

This is a problem I've been dealing with for a while now. Ideally I'd iterate through all classes and check if they implement some interfaces. If they do then execute some code on them.

Currently I have to provide some array of classes to execute e.g:

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

In an ideal world I would do something like this:

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

I don't expect there is any easy way to do this, but I have done a little research/exploration of the VBA runtime memory... I think it should be possible and have found some VB6 examples of this using the VBA6.DLL, however, unfortunately this DLL is not provided in Microsoft Office natively. However, the VBA6.DLL is likely 'compiled into' Microsoft Office itself. So the methods/fields should also be held in memory somewhere, you just need to know where they are using pointer math (is my theory)

I don't suppose anyone has had any experience with this?

like image 428
Sancarn Avatar asked Mar 06 '26 13:03

Sancarn


1 Answers

A VB_PredeclaredId attribute makes your Class1 identifier automatically refer to a global-scope object by that name, e.g. UserForm1 is the name of a class module (one with a designer, but that part is irrelevant), and it's the name of a global, automagic object spawned by VBA at runtime, and the compiler knows Class1.DoStuff is legal because it knows Class1 has VB_PredeclaredId set to True.

Thanks to the work and contributions of Wayne Phillips (vbWatchDog) and other contributors, Rubberduck taps into this internal API.

As shown in the linked code (C#), you can get ahold of the ITypeLib for a VBA project from its References collection, by turning the pointer into a struct with this specific layout:

[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;
}

In VBA that would be a user-defined Type that might look like this:

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

Once you have the pointer to the ITypeLib, you should be able to get the VBA project's type library.

From there, you'll want to iterate the types, and from there determine whether the type's TYPEFLAGS has TYPEFLAG_PREDECLID turned on (we do this here).

Obviously this is a lot of extremely crash-prone trial-and-error coding, and I wouldn't recommend doing any of this, but in any case, it's possible, if not adviseable.

Feel free to study Rubberduck.VBEEditor.ComManagement.TypeLibs namespaces.

like image 89
Mathieu Guindon Avatar answered Mar 09 '26 05:03

Mathieu Guindon



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!