1) Хум хау.
2) Обработка событий COM-объектов — не какой-то там сверхсекретный, а вплоне открытый и документированный механизм.
Любой ActiveX-контрол предоставит тебе интерфейс
IConnectionPointContainer. Этот интерфейс служит для работы с конекшен-поинтами. Оные являются call-хабами и рассылают уведомление (а проще говоря — делают вызов обработчика) о событии всем, кто подписался на такие уведомления.
Так вот, ты запрашиваешь
IConnectionPointContainer и вызываешь метод
FindConnectionPoint, передав IID sink-интерфейса, который принимает уведомления о событиях данного контрола. Такой интерфейс для неизвестного ActveX-контрола надо найти в его
библиотеке типов, которая у любого контрола заведомо вшита прямо в нём. Искать tlb-файл не нужно: контрол предоставляет интерфейс
IProvideClassInfo, через который можно можно получить типоописание CO-класса, перебрать его интерфейсы и найти среди них sink-интерфейс (и его IID). Нужный интерфейс будет иметь установленные Impl-флаги
IMPLTYPEFLAG_FSOURCE и
IMPLTYPEFLAG_FDEFAULT.
Вызвал ты, значит, FindConnectionPoint, она тебе возвращает интерфейсный указатель на
IConnectionPoint.
У
IConnectionPoint надо вызвать метод
Advise, и этим самым
подписаться на получение уведомлений о событиях. Метод вернёт
слепок (cookie), который потом надо передавать для отписки, вызывая метод
Unadvise.
В метод
Advise надо передать указатель на
свой объект, который бы реализовывал sink-интерфейс.
Понятно, что раз конкретный контрол заранее неизвестен, заранее написать класс, который бы заведомо реализовывал нужный sink-интерфейс — невозможно.
Но есть одно но: sink-интерфейс — это disp-интерфейс, поэтому должны быть реализованными только IDispatch-методы.
А теперь магия: вместо такого объекта ты делаешь
лжеобъект: то есть в модуле создаёшь массив, который будет играть роль VTable, и в него заносишь адреса функций, выполняющих роль реализаций методов IDispatch-а. Самый интересный метод для тебя: Invoke.
Когда есть неизвестные контролы, события которых надо ловить, ты всем им в качестве event-sink-а назначаешь (вызовом Advise) свой лжеобъект. Когда события возникают, будет вызван твой метод Invoke, которому будут переданы все параметры, если такие у события есть.
В общем, можно сделать один вечноживущий sink-объект, который будет поддерживать все sink-интерфейсы для любого контрола, и в этом случае можно сделать пустые AddRef/Release. Можно сделать реальные вручную реализованные sink-объекты, с контролем времени жизни.
Первый вариант (вечноживущий объект, реализующий динамический набор интерфейсов) гибче, но нарушает регламент COM (набор поддерживаемых интерфейсов объекта должен быть постоянным в течение всего времени жизни объекта). Второй более походит на VBControlExtender, но с гораздом меньшим числом посредников.
А по сути это ручная реализация того, что делается автоматически при использовании WithEvents и/или VBControlExtender.