Задача:
Обеспечить возможность использования СOM-компонентов (DLL-серверов) инсталлированных локально в каталоге приложения без (обязательной) предварительной регистрации в реестре.
С такими COM-компонентами должны работать клиенты, написанные с помощью
ATL/MFC.По возможности, должна обеспечиваться поддержка любых других COM-клиентов и
COM-серверов выполняющиеся в адресном пространстве приложения без необходимости внесения изменения в их код, например таких как .NET COM Interop (RCW/CCW в CLR), VB6-контролы, использующие другие ActiveX'ы и т.п.
Операционные системы: Win9x,NT,2000,XP,2003 Мотивация:
упрощение развертывания (включая возможность запуска приложений непосредственно с CD)
решение известных проблем версионности COM
обеспечение возможности одновременной работы с различными версиями ActiveX но с одинаковыми GUID-ами коклассов или progid.
Видимый вариант решения:
Перехват системного CoCreateInstance, по схеме "Detours".
Написание собственного CustomCoCreateInstance, который читает некоторые метаданные хранимые в каталоге .exe (аналогичные по структуре записям реестра для COM-компонентов), если находит информацию — загружает вручную DLL и выполняет цепочку действий для инстанциации COM-объекта и получения интерфейса. В случае отсутствия информации — обращается к системной CoCreateInstance.
Какие будут идеи, замечания, альтернативные предложения, дополнительные мотивации, другие пути решения?
Пара вопросов:
X>Мотивация: X>упрощение развертывания (включая возможность запуска приложений непосредственно с CD)
+1
X>решение известных проблем версионности COM
Можно подробней какие именно проблемы версионности СОМ решит этот похдод?
X>обеспечение возможности одновременной работы с различными версиями ActiveX но с одинаковыми GUID-ами коклассов или progid.
Зачем это нужно? Ведь progid и придумывали, чтобы различать версии
"For every complex problem, there is a solution that is simple, neat,
and wrong."
X>>решение известных проблем версионности COM X>>обеспечение возможности одновременной работы с различными версиями ActiveX но с одинаковыми GUID-ами коклассов или progid. AJD>Можно подробней какие именно проблемы версионности СОМ решит этот похдод? AJD>Зачем это нужно? Ведь progid и придумывали, чтобы различать версии
Собственно старая дилема:
изменять ли ProgID (и/или co-class guid) при любом изменении кода компоненты (не затрагивающей изменение интерфейса)
почему дилема?
Изменение — влечёт за собой вынужденную перекомпиляцию клиентов (или необходимость построения сложных механизмов конфигурации), хотя логически перекомпиляция не нужна, так как интерфейсы остались прежними.
Сохранение старого делает проблемным use-case установки двух продуктов использующих общий COM-компонент, если
один из продуктов ставит более свежую версию. Проблемным — так как первый продукт фактически работает в непредусмотренной (как минимум, не протестированной, конфигурации).
Ещё проблемы инсталляции/деинсталляции в случае если компонент общий (решаемые конечно проблемы, но могло бы их и не быть)
В общем, проблемы происходят из-за "системной глобатьности" реестра COM и аналогичны проблемам dll-hell.
"Локализация" такого реестра в виде тонких настроек для приложения эти проблемы решает.
Здравствуйте, xhalt, Вы писали:
X>Обеспечить возможность использования СOM-компонентов (DLL-серверов) инсталлированных локально в каталоге приложения без (обязательной) предварительной регистрации в реестре.
Собственно, меняется только использование таких объектов, а не их написание. Довольно небольшая дописка требуется для загрузки нужной библиотеки вручную, получение ее фабрики классов и затем от этой фабрики нужных коклассов, которые, как Вы справедливо заметили, в этом случае могут быть сугубо локальными (мы то точно знаем, какой файл библиотеки загрузили и реестр нас не интересует). Насколько я понимаю, это оставляет проблему регистриуемых глобально интерфейсов (type library), коклассы могут быть локальны, но наверное и этот вопрос можно решить, я просто не заморачивался.
Делал описанную вещь неоднократно, но Вас врядли заинтересует пример кода на Delphi.
Здравствуйте, softilium, Вы писали: S>Собственно, меняется только использование таких объектов, а не их написание.
Совершенно верно сами COM-сервера менять не предполагается да и лишнее это.
Однако, хотелось бы и не менять использование (детальнее поясняю ниже) S>Довольно небольшая описка требуется для загрузки нужной библиотеки вручную....
Ручная загрузка библиотеки, инстанциация объекта через фабрику и получение указателя на нужные интерфейсы — задача достаточно тривиальная. Если мы можем модифицировать код, который использует COM (заменить вызовы CoCreateInstance на CoCreateInstanceFromDll) — то вроде бы всё прозрачно.
Но есть случаи и такого типа:
[EXE] -> [Some COM Client] -> [SomeActiveX.dll]
[EXE] — наше приложение,
[Some COM Client] некий COM-клиент, который использует компоненту [SomeActiveX.dll].
Оба развёртываются в каталог приложения [EXE]. Причём [Some COM Client] —
модифицировать нельзя, так как это компонента стороннего производителя (нет исходных текстов),
или даже некая часть системы (RCW в .NET).
Таким образом задача состоит в том, чтобы заставить [Some COM Client] находить или использовать локальную [SomeActiveX.dll] не зарегистрированную в реестре.
S>Делал описанную вещь неоднократно, S>но Вас врядли заинтересует пример кода на Delphi.
Меня — нет, но, возможно, готовый пример будет кому-нибудь полезен.
1 Ты хотел сказать в хр и выше наверно?
2. Ты метаданных в каталоге приложения держать хотел — а тут они в манифесте — имхо оно удобнее, манифест это файлик, в тех осях где Registration-Free COM не работает можно эти метаданные из манифеста вытащить (а в тех где работает так и будет работать)
Здравствуйте, Юнусов Булат, Вы писали:
X>>Мимо кассы — это работает только на XP ЮБ>1 Ты хотел сказать в хр и выше наверно?
Наверное. Детально не разбирался. ЮБ>2. Ты метаданных в каталоге приложения держать хотел — а тут они в ЮБ>манифесте — имхо оно удобнее, манифест это файлик,
Под "метаданными" я тоже подразумевал нечто вроде файлика в каталоге приложения... Вообще, я некоректно употребил термин "метаданные", но "слово — не воробей" ЮБ>в тех осях где Registration-Free COM не работает можно эти метаданные ЮБ>из манифеста вытащить (а в тех где работает так и будет работать)
Да. Такой подход, пожалуй, будет вполне рационален.
Я, собственно, почему спрашиваю —
не хочеться применять грубые мотеды вроде перехвата системных функций.
Вдруг, найдутся более "честные" варианты решения.
Здравствуйте, xhalt, Вы писали:
X>Я, собственно, почему спрашиваю — X>не хочеться применять грубые мотеды вроде перехвата системных функций. X>Вдруг, найдутся более "честные" варианты решения.
Я конечно понимаю, что такой вариант не соответствует требованиям, но позвольте узнать, чем не устраивает глобальная регистрация СОМ-сервера программно в коде инициализации сервера.
AF>Я конечно понимаю, что такой вариант не соответствует требованиям, но позвольте узнать, чем не устраивает глобальная регистрация СОМ-сервера программно в коде инициализации сервера.
если к этому добавить трюк с ExeName.Exe.Local в каталоге с Exe и разрегистрацию COM-серверов при выходе из приложения — может получиться довольно дешёвое и имхо вполне надёжное решение.
Более внимательно прочитал начальное сообщение и понял, что предложил не совсем то, что надо.
> упрощение развертывания (включая возможность запуска приложений непосредственно с CD)
Это работает (для этого и делалось, главным было обойти защиту HKCR для простого пользователя). При этом регистрация происходит намного быстрее, чем вызов DllRegisterServer из DLL.
> решение известных проблем версионности COM
+/-
> обеспечение возможности одновременной работы с различными версиями ActiveX но с одинаковыми GUID-ами коклассов или progid.
Возможно только на XP ранее описанным способом. "Ломать" CoCreateInstance не советую.
PS. Без регистрации в реесте библиотеки типов не будте работать стандарная реализация IDispatch из Ole32.dll. Ее точно использует реализация IDispatchImp из ATL и, (возможно) VB.
PPS. Т.к. до Win2000 таких фокусов с реестром было делать нельзя, то в Win9x и NT4 регистация выполняется прямой записью данных в HKCR. "Выдиралка" работает только в Win2000/XP.
Здравствуйте, al, Вы писали:
>> обеспечение возможности одновременной работы с различными версиями ActiveX но с одинаковыми GUID-ами >> коклассов или progid. >Возможно только на XP ранее описанным способом.
>"Ломать" CoCreateInstance не советую. >PS. Без регистрации в реесте библиотеки типов не будте работать стандарная реализация IDispatch из > Ole32.dll. Ее точно использует реализация IDispatchImp из ATL и, (возможно) VB.
Угу. Теоретические исследования и практические эксперименты показали что
в тривиальных случаях всё работает как и ожидается, однако при "лобовом" решении:
— имеются проблемы с typelib-маршалером
— имеются проблемы с созданием proxy/stub (если они в отдельной DLL)
— игнорируется ThreadingModel (не выполняется "проксирование" фабрики классов при несоответсвии аппартмента)
(могут быть неожиданные проблемы с синхронизацией с компонентами с apartment-моделью)
(К слову: в Wine и ReactOS ThreadingModel тоже игнорируется)
Всё это преодолевается, но уж больно кода много придётся писать/отлаживать.
В данный момент разрабатываю чуть иную стратегию, которую изложу здесь чуть позже.
>PPS. Т.к. до Win2000 таких фокусов с реестром было делать нельзя, то в Win9x и NT4 регистация выполняется >прямой записью данных в HKCR. "Выдиралка" работает только в Win2000/XP.
Кстати, моя "выдиралка", которая предназначена для сохранения регистрационной информации (названную в оригинальном посте "метаданными") работает тоже по такому принципу (RegOverridePredefKey).
Здравствуйте, Alexey Frolov, Вы писали:
AF>Здравствуйте, xhalt, Вы писали:
X>>Я, собственно, почему спрашиваю — X>>не хочеться применять грубые мотеды вроде перехвата системных функций. X>>Вдруг, найдутся более "честные" варианты решения.
AF>Я конечно понимаю, что такой вариант не соответствует требованиям,
На то оно и обсуждение AF>но позвольте узнать, чем не устраивает глобальная регистрация СОМ-сервера AF>программно в коде инициализации сервера.
Собственно не устраивает сама _глобальность_ регистрации. Если бы можно было бы регистрировать локально для приложения (например для текущего сенаса запуска) — было бы то что нужно.
С "глобальность" не совместим use-case с развертыванием DLL внутри каталога exe-приложения (xcopy-, ClickOnce-, и т.п. deployment),
при наличии двух инсталляций (разных) программных продуктов использующих ком-объект с одинаковым CLSID.
Особенно актуально при использовании компонент сторонних производителей (которых нельзя или сложно изменять)
Это и подрузумевалось в оригинальном посте: "обеспечение возможности одновременной работы с различными версиями ActiveX но с одинаковыми GUID-ами коклассов или progid"
Иными словами, COM (по крайней мере до Windows XP) ограничивает выбор программиста,
не давая возможности инсталлировать депендсы приложения изолированно, т.е. исключая какое-либо влияние на другие приложения.
X>В данный момент разрабатываю чуть иную стратегию, которую изложу здесь чуть позже.
Я в свое время думал о перехвате API-функций работы с реестром — в этом случае можно каждому процессу "подсунуть" "виртуальный реестр" с нужными нам данными рпеистрации. Я думаю, что этот путь полностью решит все Ваши задачи, но я от него отказался по двум причинам:
1) Мои задачи решилисть протой запсью в HKCU\Software\Classes
2) Перехват API-функций, с моей точки зрения — хак, требующий достаточно сложной реализации, отладки и сопровождения.
X>С "глобальность" не совместим use-case с развертыванием DLL внутри каталога exe-приложения (xcopy-, ClickOnce-, и т.п. deployment), X>при наличии двух инсталляций (разных) программных продуктов использующих ком-объект с одинаковым CLSID. X>Особенно актуально при использовании компонент сторонних производителей (которых нельзя или сложно изменять)
Всё это решается с помощью трюка с MyFile.Exe.Local — он работает начиная с Win98, то бишь на всех поддерживаемых сейчас WIndows-платформах
Здравствуйте, al, Вы писали:
X>>В данный момент разрабатываю чуть иную стратегию, которую изложу здесь чуть позже.
al>Я в свое время думал о перехвате API-функций работы с реестром — в этом случае можно каждому al>процессу "подсунуть" "виртуальный реестр" с нужными нам данными рпеистрации. al>Я думаю, что этот путь полностью решит все Ваши задачи...
Ага. Вот фактически я к этому и пришёл.
Резюмируя всё вышесказанное — имеем такую вот стратегию:
1. В каталог нашего приложения our.exe
размещаем файл-флаг our.exe.Local
и файл манифест our.exe.manifest, в последний помещаем информацию о регистрации компонент
как это указано здесь.
2. Во начале работы приложения (перед началом использования COM-компонент),
выполняем проверку операционной системы и если она поддерживает registration-free COM — то на этом и останавливаемся
3. Выполняем перехват API-функций работы с реестром локально для текущего процесса. Подсовываем, как сказал al,
"виртуальный реестр" с нужными нам данными. Учитывая, что API-функции работы с реестром часто используемы, следует использовать методику перехвата, которая даёт минимально возможное замедление, и, скорее всего, какую-либо разновидность хеш-поиска для проверки принадлежности запрашиваемых ключей/значений к множеству наших.
Ничего не забыл?
>1) Мои задачи решились протой запсью в HKCU\Software\Classes
Если для решения определённой задачи можно ограничиться этим — то несомненно, так и нужно поступить
>2) Перехват API-функций, с моей точки зрения — хак, требующий достаточно сложной реализации, >отладки и сопровождения.
Я отношусь к таким хакам как к решениям, к которым следует прибегать лишь в крайних случаях (когда ничего другого не остаётся).
Как вариант, можно восползоваться готовыми и уже проверенными решениями (например, Detours).
Кстати, в данном конкретном случае, не исключено, что можно ограничиться простой корректировкой
таблиуы импорта ole32.dll
Здравствуйте, xhalt, Вы писали:
X>Резюмируя всё вышесказанное — имеем такую вот стратегию:
X>1. В каталог нашего приложения our.exe X>размещаем файл-флаг our.exe.Local X>и файл манифест our.exe.manifest...