VB6 / VBA Итерация по всем предварительно объявленным объектам класса - PullRequest
2 голосов
/ 10 октября 2019

Могу ли я динамически перебирать все предварительно объявленные объекты?

Это проблема, с которой я сталкиваюсь уже некоторое время. В идеале я бы перебрал все классы и проверил, реализуют ли они некоторые интерфейсы. Если они затем выполнят некоторый код для них.

В настоящее время я должен предоставить некоторый массив классов для выполнения, например:

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 ... Я думаю, что это должно быть возможно, и нашел какой-то VB6примеры этого с использованием VBA6.DLL, однако, к сожалению, эта DLL не предоставляется в Microsoft Office изначально. Однако VBA6.DLL, скорее всего, «скомпилирован» в сам Microsoft Office. Таким образом, методы / поля также должны храниться где-то в памяти, вам просто нужно знать, где они используют указатель математики (моя теория)

Я не думаю, что кто-то имел опыт с этим?

Ответы [ 2 ]

5 голосов
/ 10 октября 2019
Атрибут

A VB_PredeclaredId позволяет вашему идентификатору Class1 автоматически ссылаться на объект глобальной области с таким именем, например, UserForm1 - это имя модуля класса (один с конструктором, но эта часть не имеет значения), и это имя глобального, автоматизированного объекта, порожденного VBA во время выполнения, и компилятор знает, что Class1.DoStuff допустимо , потому что знает, что Class1 имеет VB_PredeclaredId, установленное в True.

Благодаря работе и вкладу Уэйна Филлипса (vbWatchDog) и других участников, Rubberduck подключается к этому внутреннему API .

Как показано в связанном коде(C #), вы можете получить ITypeLib для проекта VBA из его коллекции References, превратив указатель в структуру с этим конкретным макетом:

[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 (мы делаем это здесь ).

Очевидно, это много экстремального сбоя- пробное и ошибочное кодирование, и я бы не рекомендовал делать это, но в любом случае это возможно , если не рекомендуется.

Не стесняйтесь учиться Rubberduck.VBEEditor.ComManagement.TypeLibs пространства имен.

0 голосов
/ 10 октября 2019

Я не знаю, как использовать tlbinf32.dll, чтобы получить информацию о классе напрямую из памяти, однако, если вы придерживались текущего подхода, но использовали массив объектов вместо массива объектов, вы могли бы сделатьследующее:

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 

Конечно, это будет зависеть от того факта, что эти классы должны быть созданы с помощью «затемнения» их как интерфейса, который они реализуют следующим образом:

Dim bar As ITask

Set bar = New Task_Class1

SideNote: Если вам интересно, да, мне не хватает названия методов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...