Универсальный Индексер

Персональный блог одноименного форумчанина. Человека и парохода, не побоюсь этого сравнения :)

Модератор: tyomitch

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Универсальный Индексер

Сообщение tyomitch » 31.10.2006 (Вт) 22:00

Вас никогда не поражала магия VB6? Представим себе такой код:

Код: Выделить всё
Dim w As Object, v
    Set w = New Class1
    v = w.a(12)(34)
    v = w.b(12)(34)


Что означает запись "(34)"? Если вызванный метод вернёт объект, то это будет вызов его свойства по умолчанию; а если он вернёт массив, то это будет взятие элемента массива.
Код: Выделить всё
' Содержимое Class1
Option Explicit

Public Function a(x)
    Set a = Me
End Function

Public Function b(x)
    b = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
End Function

' Метод по умолчанию
Public Function c(x)
    c = x * 2
End Function

Приведённый вначале код присвоит в v сначала с(34)=68, а затем 34-ый элемент массива внутри b (пятёрку).

Ясно, что поскольку связывание позднее, компилятор не знает заранее, будет "(34)" применено к массиву или объекту. Код в обоих случаях генерируется одинаково универсальный.

То же верно и для нескольких индексов в правых скобках (просто лень было писать тестовый пример).

Как это делается? и как нам сделать то же самое?

Предположим, у нас есть вариант (с неизвестным содержимым) и набор индексов для него (в массиве вариантов). Раз число индексов неизвестно заранее, значит готовой магией мы воспользоваться не можем. Как реализовать аналогичную функциональность?

Готовая магия компилируется в вызов функции __vbaVarIndexLoad; но увы -- это cdecl-функция с переменным числом аргументов, так что вызывать её через Declare не удастся. Единственный выход -- писать переходник.

Код: Выделить всё
Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Sub Call_VarIndexLoad Lib "user32" Alias "CallWindowProcA" (lpProc As Any, Result As Variant, Source As Variant, ByVal cIndices As Long, ByVal pIndices As Long)

Public Function VarIndexLoad(Source As Variant, Indices() As Variant) As Variant
Dim code(14) As Long
code(0) = &H56EC8B55
code(1) = &H10758B57
code(2) = &HE0D1C68B
code(3) = &HC88BE0D1
code(4) = &HE0D1E0D1
code(5) = &HFC8BE02B
code(6) = &H14758B56
code(7) = &HF08BA5F3
code(8) = &HFF0C75FF
code(9) = &H15FF0875
code(10) = VarPtr(code(14))
code(11) = &H30CC483
code(12) = &HC95E5FE6
code(13) = &H10C2
code(14) = GetProcAddress(GetModuleHandle("msvbvm60.dll"), "__vbaVarIndexLoad")
Call_VarIndexLoad code(0), VarIndexLoad, Source, UBound(Indices) - LBound(Indices) + 1, VarPtr(Indices(LBound(Indices)))
End Function

' использование:
Dim Indices() As Variant
    Indices = Array(34)
    v = VarIndexLoad(w.a(12), Indices)
    v = VarIndexLoad(w.b(12), Indices)

Готово! у нас есть Универсальный Индексер, принимающий переменное число индексов. Даже если мы знаем, что Source -- это объект, нет никакого стандартного способа вызвать его свойство по умолчанию с переменным числом аргументов. Собственно, для этого ограниченного случая я свой код и писал; уже потом я сообразил, что он вышел универсальнее, чем мне нужно было.


Ну, и вопрос на засыпку: а как вызвать метод объекта по имени, если число его аргументов неизвестно заранее? В лоб, через CallByName, очевидно, не удастся. Завтра, если никто не догадается сам, запощу отгадку :-)
Изображение

keks-n
Доктор VB наук
Доктор VB наук
Аватара пользователя
 
Сообщения: 2509
Зарегистрирован: 19.09.2005 (Пн) 17:17
Откуда: г. Москва

Сообщение keks-n » 01.11.2006 (Ср) 1:01

IDispatch->GetTypeInfo. Далее - ищем искомый метод и получаем инфу о параметрах. После чего формируем Variant-массив агрументов, который скармливаем CallByName.
Изображение

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 01.11.2006 (Ср) 9:47

CallByName не принимает массив аргументов. Она принимает их все через запятую.
Изображение

keks-n
Доктор VB наук
Доктор VB наук
Аватара пользователя
 
Сообщения: 2509
Зарегистрирован: 19.09.2005 (Пн) 17:17
Откуда: г. Москва

Сообщение keks-n » 01.11.2006 (Ср) 11:38

Object Browser писал(а):Function CallByName(Object As Object, ProcName As String, CallType As VbCallType, Args() As Variant)
Изображение

GSerg
Шаман
Шаман
 
Сообщения: 14286
Зарегистрирован: 14.12.2002 (Сб) 5:25
Откуда: Магадан

Сообщение GSerg » 01.11.2006 (Ср) 11:46

keks-n, ты её никогда не использовал, правда?..
Как только вы переберёте все варианты решения и не найдёте нужного, тут же обнаружится решение, простое и очевидное для всех, кроме вас

keks-n
Доктор VB наук
Доктор VB наук
Аватара пользователя
 
Сообщения: 2509
Зарегистрирован: 19.09.2005 (Пн) 17:17
Откуда: г. Москва

Сообщение keks-n » 01.11.2006 (Ср) 12:21

Тады вручную вызывать, через IDispatch->Invoke. Там, правда, набор параметров кошмарный.
Изображение

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 01.11.2006 (Ср) 12:30

Ага. А может, есть что попроще? Без TLB и без прочих морок? ;-)
Изображение

keks-n
Доктор VB наук
Доктор VB наук
Аватара пользователя
 
Сообщения: 2509
Зарегистрирован: 19.09.2005 (Пн) 17:17
Откуда: г. Москва

Сообщение keks-n » 01.11.2006 (Ср) 13:58

Подключаем ScriptControl, формируем скрипт, вызывающий CallByName с нужным количеством параметров :roll:
Изображение

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 01.11.2006 (Ср) 20:10

На самом деле, CallByName принимает именно массив аргументов.
Просто он в TLB объявлен как ParamArray, поэтому передать туда массив напрямую не удаётся.
Но никто ведь не мешает её переопределить, верно? :-)
Вот и отгадка. Проверьте -- работает.
Код: Выделить всё
Private Declare Function CallByNameAndArgsArray Lib "msvbvm60" Alias "rtcCallByName" (ByVal Object As Object, ByVal ProcName As Long, ByVal CallType As VbCallType, Args() As Variant, Optional ByVal lcid As Long) As Variant
Изображение

keks-n
Доктор VB наук
Доктор VB наук
Аватара пользователя
 
Сообщения: 2509
Зарегистрирован: 19.09.2005 (Пн) 17:17
Откуда: г. Москва

Сообщение keks-n » 02.11.2006 (Чт) 0:13

А lcid - это что? Туда, часом не 1251 передавать надо?
Изображение

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 02.11.2006 (Чт) 11:13

А lcid есть и у IDispatch->Invoke. Вот ты и попался: советовал использовать метод, который сам в глаза не видел ;-)
Изображение

keks-n
Доктор VB наук
Доктор VB наук
Аватара пользователя
 
Сообщения: 2509
Зарегистрирован: 19.09.2005 (Пн) 17:17
Откуда: г. Москва

Сообщение keks-n » 02.11.2006 (Чт) 11:29

Видел. lcid - итендификатор языка, кажись. Всегда проставлял из примера, о значении не задумывался
Изображение


Вернуться в Tyomitch

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 5

    TopList