Предложение Заценить и Высказаться остаётся в полной силе.
Как мы все знаем, VB всё делает за нас при работе с объектами. После
- Код: Выделить всё
Dim obj As Object
Set obj = something
можно просто вызывать методы: obj.method1, obj.method2 - VB справляется с такими вызовами, даже если у него нет описания данного объекта. Как у него это получается? Очень просто. У него это не получается Всё дело в том, что Object в VB - это всегда такой объект (COM-объект), который реализует интерфейсы IUnknown и IDispatch. Через IUnknown VB спрашивает: "Поддерживаешь IDispatch?". "Yep...". VB переходит на общение по IDispatch. "Есть метод по имени 'method1'?". "Есть!". "Параметры?". "Такие-то!". "Ага, совпадает... Тогда лови!..". Не будем говорить о том, сколько времени уходит на подобные диалоги, ни к чему это За лёгкость надо платить
Но существует куча прекрасных объектов, реализующих IUnknown (его реализуют абсолютно все COM-объекты), но не реализующих IDispatch! Диалог с такими объектами либо завершается на стадии "Поддерживаешь IDispatch?". "Oops...", либо приводит к относительно допустимому сворачиванию коврика. Не имея механизма опроса объекта на предмет его методов, VB делает вывод, что работать с ним ну никак нельзя. Но когда это нас останавливало встроенное органичение?
Начнём с основ, чтобы не создавать смутного и невразумительного впечатления
Вот создали мы описание класса (в VB или ещё где - не суть). Вот создали экземпляр этого класса, и ещё один - да много насоздавали мы экземпляров... У каждого экземпляра свои внутренние данные, каждый обладает методами. Так вот, данные у каждого экземпляра действительно свои, а вот методы у всех экземпляров общие. Какой бы экземпляр не вызвал метод - вызов пойдёт в одну и ту же точку кода. Что, в общем, логично - откомпилированный код процедуры не должен изменяться (хотя можно пошалить и тут), зачем же его клонировать. А раз у всех методы общие, значит, каждый должен иметь представление о том, где эти общие находятся. Это представление называется vTable. Это такая таблица (одномерный массив, если угодно), каждый элемент которого имеет размер 4 байта и является указателем на метод. Все методы, поддерживаемые объектом, идут в этой таблице подряд. Упорядочены они по интерфейсам (сначала все методы одного интерфейса, потом все другого...), а в рамках конкретного интерфейса порядок методов определяется порядком описания этих методов в исходнике. Первым идёт всегда интерфейс IUnknown, а его методы - всегда в порядке: QueryInterface, AddRef, Release. Только так. Всегда и везде. Благодаря этим трём фиксированным указателям, этим 12 байтам, собственно, и живёт технология COM
Те, кто смотрел проект VB + ASM, наверняка уже догадались "Таблица указателей? Но мы же уже умеем вызывать по указателю!". В точечности так. Осталось подкорректировать совсем немного.
Как получить указатель на vTable? Очень просто. Интерфейс - это и есть указатель на vTable.
Вот он, указатель pInterface, вернутый нам апишкой. Что будем с ним делать?
- Код: Выделить всё
GetMem4 pInterface, VarPtr(vTable) 'дереференс: находим положение vTable
И вот мы уже в начале таблицы. Дальше? А дальше идём в мануал. И в этом мануале читаем описание этого интерфейса. Если нет мануала, лезем в .h. В общем, узнаём, каким же идёт интересный нам метод. Узнали? Метод номер 6? Методы нумеруются с нуля! Так что метод номер 5. Чтобы больше не лезть никогда в этот мануал, определяем константу типа
- Код: Выделить всё
Const MYINTERAFACE_MYMETHOD As Long = 5
Метод номер 5. Размер указателя 4 байта. А мы в начале таблицы. А они все подряд. Так что, само собой,
- Код: Выделить всё
GetMem4 vTable + MYINTERAFACE_MYMETHOD * 4, VarPtr(MyMethodPointer)
Ну что, дружно идём вызывать MyMethodPointer через cFuncCall.CallFunction? Уже пошли? Молодцы! А почему не сохранились? Но уже поздно, в любом случае Потому что любой метод интерфейса на клеточном, так сказать, уровне имеет на один параметр больше, чем описано в мануале. Параметр этот - указатель this (сишники в восторге), он же Self (дельфисты счастливы), он же Me (ну как же про нас забыть). И параметр этот идёт всегда первым. Собственно, благодаря этому параметру общий для всех экземпляров код определяет, какой именно экземпляр его вызвал.
Вот теперь у нас достаточно информации для того, чтобы немного мутировать CallFunction в CallInterface. Все дружно идём в VB + ASM и смотрим обновлённый класс, данную фичу содержащий
Сколько раз рушилась IDE, пока я разобрался с уровнями дереференса - это отдельный разговор...
В качестве теста я тут попробовал от RichTextBox получить IRichEditOle - и вы знаете, получил И метод вызвался, собака
А возможность вручную управлять временем жизни объектов? AddRef - номер 1, Release - номер 2! И больше никаких неуничтоженных, знаете ли, объектов... Захотим уничтожить - будем вызывать Release, пока не сдохнет... Если, конечно, мы точно знаем, что никто в это время не попытается вызвать объект, а то коврик, знаете ли...