Vasilisk » 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 будет формально соблюдена? Поэтому системе ничего не остаётся, как и самой строго формально _всегда_ следовать спецификации. Что она и делает...
Афтор
Я не был бы столь категоричен, как предыдущий оратор :)
Понял вопрос с трудом. Но постараюсь ответить. Правило первое - если клиент запрашивает у сервера ссылку, то она должна уйти за пределы сервера с уже продвинутым счётчиком ссылок, т.е. можно полагать, что объект рождается со счётчиком ссылок равным единице. Иначе не сделать - объект с нулевым счётчиком ссылок должен быть уничтожен. Из этого следует, что в клиенте число вызовов 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 будет формально соблюдена? Поэтому системе ничего не остаётся, как и самой строго формально _всегда_ следовать спецификации. Что она и делает...
Афтор