Особенности CoCreateInstance

Общие вопросы, не зависящие от языка реализации.

Модераторы: Duncon, Eugie, Romeo, Hawk

Ответить
Booster
Сообщения: 5
Зарегистрирован: 17 май 2005, 16:04
Откуда: Санкт-Петербург

18 май 2005, 12:22

Изучаю создание COM сервера, по статьям "Что такое "технология COM" Михаила Безверхова.
При получении указателя на объект с помощью "CoCreateInstance", если в фабрике класса я просто передаю указатель на экземпляр объекта и не вызываю никаких других методов, то ОСЬ зачем-то сначала сама вызывает методы экземпляра AddRef, Release. Затем QueryInterface, Release. Ранее в SDK нашёл пример в котором в фабрике класса для получения указателя используется вызов QueryInterface, а в этом методе AddRef. И не мог понять почему делается именно так. Теперь понял почему. Так как при таком поведении Оси, только так будет нормально функционировать счетчик ссылок. НО у меня вопрос: почему сделано именно так? Или это надо понимать просто как данность?
Аватара пользователя
Romeo
Сообщения: 3091
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

18 май 2005, 18:25

Это существующая идеология и она функционирует. Какие могут быть ещё вопросы :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Vasilisk
Сообщения: 111
Зарегистрирован: 13 фев 2004, 18:43

18 май 2005, 22:04

Я не был бы столь категоричен, как предыдущий оратор :)

Понял вопрос с трудом. Но постараюсь ответить. Правило первое - если клиент запрашивает у сервера ссылку, то она должна уйти за пределы сервера с уже продвинутым счётчиком ссылок, т.е. можно полагать, что объект рождается со счётчиком ссылок равным единице. Иначе не сделать - объект с нулевым счётчиком ссылок должен быть уничтожен. Из этого следует, что в клиенте число вызовов Release для этого объекта всегда будет на единицу меньше, чем AddRef - самый-самый первый раз AddRef вызывал ещё сервер. И это правило реализуется и в QueryInterface тоже - всякая ссылка из неё исходящая "защёлкнута".

Правило второе - COM-объект может реализовывать более одного интерфейса, при этом как технически он их будет реализовывать (на одной большой vtbl или на нескольких маленьких и совершенно несвязных) - дело сервера. А поэтому у клиента нет никакой корректной возможности перейти от одного интерфейса к другому не вызывая QueryInterface. Строго говоря: 1)любая vtbl должна в первых своих трёх строках содержать интерфейс IUnknown; 2) методы этого интерфейса должны быть одинаковыми для всех интерфейсов данного объекта; 3)при запросе интерфейса IUnknown (из любого интерфейса)объект всегда должен возвращать адрес одной и той же vtbl. Если у объекта несколько интерфейсов, реализованных каждый на своей vtbl, то при запросе этих интрефейсов и вернутся разные адреса. Не будь третьего требования, то и при запросе IUnknown можно было бы возвращать адрес любой vtbl - во всех них одни и те же его методы. Но требование есть и нарушать его нельзя - имея два указателя на COM-объекты клиент может узнать ссылаются ли они на один и тот же объект (может быть - это указатели на разные интерфейсы одного объекта?) или на разные. Для этого он должен запросить ссылки на IUnknown у обоих и сравнить - если адреса IUnknown окажутся одинаковы, то это - интерфейсы одного и того же объекта, вот для чего существует третье правило.

Но оно же, это третье требование, не позволяет корректно перейти от IUnknown к производному интерфейсу простым преобразованием типа в клиенте - а вдруг имеющийся у меня IUnknown не из той vtbl, где и требуемый мной теперь интерфейс? Вот от интерфейса IX * ptr перейти к IUnknown легко - reinterpret_cast<IUnknown *>(ptr), но обратное-то из-за сказанного выше - неверно. Поэтому унифицированное избежание возможного геморроя предполагает, что при любом переходе между интерфейсами надо вызывать QueryInterface, пусть её вызов в каком-то очень конкретном случае ничего фактически и не делает, возвращая адрес той же самой vtbl. Во всех других случаях он _может_ вернуть совсем другой адрес.

Правило третье (и это в статьях было описано подробно) - CoCreateInstance это внутри обращение не к одному объекту, а к двум. Нормальное "ручное" создание объекта складывается из двух фаз: нужно создать объект фабрики класса, и у него попросить создать нестатический экземпляр этого класса - тот самый "COM-объект", который и есть "объект пользователя". Но фабрика класса - сама COM-объект... Поэтому система сначала создаёт фабрику класса, "защёлкивает" у неё ссылку IUnknown, запрашивает у этого IUnknown по QueryInterface ссылку на IClassFactory и уже по этой второй ссылке - вызывает метод CreateInstance... Т.е. вот так:

IUnknown * objfact;
IClassFactory * fact;
MyObj * myob;

CoGetClassObject(...&objfact); //ссылку на объект фабрики первый раз защёлкнет DllGetClassObject
objfact->QueryInterface(IID_IClassFactory,&fact); //ccылку на него же защёлкнет QueryInterface
objfact->Release();
fact->CreateInstance(...&myobj); //ссылку на MyObj защёлкнет CreateInstance
fact->Release();

return myobj;

Делая так система дует на воду - в большинстве реализаций объекта фабрики класса всего один собственный интерфейс, который наследует обязательному же IUnknown. И они естественно и прекрасно обходятся только одной vtbl-ю (меньше уже нефозможжно). QueryInterface в таком случае совершенно лишний. Но фабрика-то класса реализуется не системой, а - программистом. И вдруг он её реализует со множеством интерфейсов и пр.? Ну, мало ли ему будет надо, при всём том, что спецификация COM будет формально соблюдена? Поэтому системе ничего не остаётся, как и самой строго формально _всегда_ следовать спецификации. Что она и делает...

Афтор
Аватара пользователя
Romeo
Сообщения: 3091
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

18 май 2005, 22:16

Браво, очень развёрнуто. У меня не хватило времени на то, чтобы вдаться в такие подробности: одной строчки, хоть и достаточно категоричной по словам Афтора, в данном случае более, чем достаточно :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Vasilisk
Сообщения: 111
Зарегистрирован: 13 фев 2004, 18:43

18 май 2005, 22:56

Замеченные опечатки. В фразе "Из этого следует, что в клиенте число вызовов Release для этого объекта всегда будет на единицу меньше, чем AddRef - самый-самый первый раз AddRef вызывал ещё сервер. "

следует читать "на единицу больше".
Аватара пользователя
Romeo
Сообщения: 3091
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

19 май 2005, 10:55

Пердлагаю нажать кнопку "Правка", оно попродуктивнее будет :)
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Booster
Сообщения: 5
Зарегистрирован: 17 май 2005, 16:04
Откуда: Санкт-Петербург

19 май 2005, 13:57

2 Vasilisk
Спасибо за пояснения.
Получается что система сама, дополнительно, вызывает QueryInterface с IID_IClassFactory, а не просто вызывает метод vtbl экземпляра фабрики, переданного ей функцией DllGetClassObject. Но по моим наблюдениям, подобное происходит и для экземпляра преданного системе, функцией фабрики CreateInstance. Но ведь из фабрики, уже был вызов QueryInterface для нужного интерфейса, зачем тогда система вызывает его повторно, и нужно ли это учитывать в реализации QueryInterface?
И ещё как нужно вести подсчёт ссылок в экземпляре фабрики? Или всё-же система сама его уничтожает, после того как надобность в нём у неё отпадает, независимо от того что возвращается в методах AddRef, Release?
3)при запросе интерфейса IUnknown (из любого интерфейса)объект всегда должен возвращать адрес одной и той же vtbl
Это значит что нужно создать отдельный экземпляр и по запросу IUnknown, выдавать всегда именно его?
Ответить