Построение иерархии объектов DCOM сервера.
Использую CBuilder 6 для построение DCOM сервера. Как и учат в "CBuilder Энциклопедия программиста" и других подобных книгах, создаю Automation Object из закладки ActiveX, вопросов это всё никаких не вызывает. Но все тестовые примеры, во всех статьях и книгах, всё это всегда строится на одном интерфейсе одного объекта.
Проблема у меня следующая.
Допустим есть Interface1 и Interface2. При этом Interface1 должен содержать атрибут — Interface2. Клиент должен иметь возможность только создать объект, реализующий Interface1. А через него достучаться до Interface2, как до атрибута созданного объекта.
При этом он не должен иметь возможность получить Interface2 до того, как получит Interface1. При этом хорошо бы, чтобы класс, реализующий Interface2, имел доступ до данных класса, реализующего Interface1 (т.е. являлся его составляющим).
Как же это сделать? Мучаюсь с четверга...
С уважением, Аркадий.
Клиент должен иметь возможность только создать объект, реализующий Interface1. А через него достучаться до Interface2, как до атрибута созданного объекта.
Если я вас правильно понял - то вы пытаетесь и рыбку съесть и гедонистические удовольствия получить. :)
В вашем случае используйте паттерн "Фабрика классов" - по крайней мере спросонья мне кажется это удачной идеей.
В вашем случае используйте паттерн "Фабрика классов" - по крайней мере спросонья мне кажется это удачной идеей.
По-моему это не совсем то.
Я имел ввиду такое.
Есть два интерфейса
{
virtual HRESULT AsString(BSTR* Str/*[out]*/) = 0;
virtual HRESULT AsINT(VARIANT* INT/*[out]*/) = 0;
};
interface IDouble : public IDispatch
{
virtual HRESULT DoubleIt(VARIANT Num/*[in]*/) = 0;
virtual HRESULT get_Result(IReturn** Num/*[out]*/) = 0;
};
Первый умеет выводить строчку или число, а второй удваивает введенное число и предоставляет пользователю первый интерфейс, чтобы тот мог выбрать, в каком виде он хочет получить результат (строчкой или числом). Это пример.
Очевидно, что первый интерфейс клиент не должен получить без второго, потому что сам по себе он не имеет смысла.
Но клиент должен иметь возможность получить первый интерфейс и уметь получить через него второй интерфейс, чтобы увидеть результат вычислений.
При этом класс, реализующий второй интерфейс, должен как-то иметь доступ до действий первого.
Вне DCOM и type library - это обычное дело, почти все классы устроены таким образом, но как тут сделать всё это правильно я не знаю, в статьях и книгах не нашел...
Честно говоря, Фабрику классов реализую там не я, её, вроде как, предоставляет мне Борланд, а как сказать ему, чтобы он не давал создавать первый интерфейс не представляю. Как это сделать?
Если убрать у CoClass галочку "Can Create" или добавить "Hidden" или и то, и то - ничего не меняется, клиент как мог, так и может загружать этот объект сразу.
Кроме того, надо, чтобы первый интерфейс был именно property второго, насколько я понимаю, это отметает вариант получать первый интерфейс через QueryInterface от второго, только как property второго.
А значит, чтобы следовать нотации КОМ, эти интерфейсы должны быть реализованы разными объектами (потому что иначе они обязаны получать друг друга через QueryInterface).
в вижуале это выглядит примерно так:
{
[propget, id(1), helpstring("property Sett")] HRESULT Sett([out, retval] IReturn** pVal);
[propputref, id(1), helpstring("property Sett")] HRESULT Sett([in] IReturn* newVal);
};
в вижуале это выглядит примерно так:
{
[propget, id(1), helpstring("property Sett")] HRESULT Sett([out, retval] IReturn** pVal);
[propputref, id(1), helpstring("property Sett")] HRESULT Sett([in] IReturn* newVal);
};
Спасибо, как получить свойство - проблемы не вызывает. Вопрос вызывает, как сделать так, чтобы класс, являющийся свойством, нельзя было создать извне. Чтобы интерфейс IReport можно было получить ТОЛЬКО дернув его, как свойство интерфейса IDouble..
Если убрать у CoClass галочку "Can Create" или добавить "Hidden" или и то, и то - ничего не меняется, клиент как мог, так и может загружать этот объект сразу.
Вроде так или так:
1. Убрать создание лишней фабрики при инициализации
2. Унаследоваться от борландовской фабрики и переопределить ее методы создания (сейчас названий не вспомню)
Кроме того, надо, чтобы первый интерфейс был именно property второго, насколько я понимаю, это отметает вариант получать первый интерфейс через QueryInterface от второго, только как property второго.
А значит, чтобы следовать нотации КОМ, эти интерфейсы должны быть реализованы разными объектами (потому что иначе они обязаны получать друг друга через QueryInterface).
Как я вижу решение - так это первый интерфейс будет порождаться от второго либо через QueryInterface, либо через property. Помоему так это самая что ни на есть нотация КОМ. Хотя в ней даже без свойств обходятся.
Проект с IReport и IDouble мона сделать за 3 мнуты тому, кто умеет, там же не вопрос кодинга. Если его сделать - это будет первое место в интернете, где есть пример проекта, где клиент не может создать IReport без IDouble.
Правда, конечно, хотелось бы, чтобы это всё-таки был не один объект, поддерживающий кучу интерфейсов, а именно их иерархия объектов... неужели нельзя это сделать, запретив создавать объекты пользователю? =(
Флаг Can Create просто так не работает, что надо сделать, чтобы заработал? :(
...
Как же это сделать?
...
по моему надо почитать литературу на тему - агрегирование
В таком случае даже если реально это несколько объектов, для клиента это всё равно будет как один объект, имеющий 40 интерфейсов.
А нужно, чтобы и пользователь видел эти два интерфейса, как несколько объектов, один из которых он может получить только через другой.
Кроме того, даже если бы агрегирование подходило (в некоторых местах это подойдет), как оно устроено я знаю, а вот как сказать билдеру агрегировать один объект через другой - я не знаю и не помню, чтобы когда-либо где-то натыкался на статью об этом.
Тут я ошибся. Убирая создание фабрики лишаешься возможности не только создавать объект извне, но и изнутри (создание тоже идет через фабрику, слегка иным способом но все же). Вот что у меня получилось на скорую руку. Проект на дельфи, билдера нет и в ближайшем будущем не предвидится. Надеюсь сумеешь переделать. Пришлось подменять DllGetClassObject.
см. исходники (документацию) ATL
Вот как я бы сделал в жизни:
{
virtual HRESULT AsString(BSTR* Str/*[out]*/) = 0;
virtual HRESULT AsINT(VARIANT* INT/*[out]*/) = 0;
};
interface IDouble
{
virtual HRESULT DoubleIt(VARIANT Num/*[in]*/) = 0;
virtual HRESULT get_Result(IReturn** Ret/*[out]*/) = 0;
};
class CReturn : public IReturn
{
private:
int m_X;
public:
void Init(int X);
public: //Interface
virtual HRESULT AsString(BSTR* Str/*[out]*/);
virtual HRESULT AsINT(VARIANT* INT/*[out]*/);
/*думаю реализация понятна и приводить смысла нет*/
};
class CDouble : public IDouble
{
private:
int m_Y;
CReturn m_Return;
public: //Interface
virtual HRESULT DoubleIt(VARIANT Num)
{
m_Y = Num.intVal + Num.intVal;
return S_OK;
}
virtual HRESULT get_Result(IReturn** Ret/*[out]*/)
{
m_Return.Init(m_Y);
*Ret = static_cast<IReturn*>Ret;
return S_OK;
}
};
При этом пользователь может получить интерфейс IReturn только через IDouble. И никак иначе. И при этом из одного класса в другой идёт передача данных (потому, что они связаны).
Теперь представим, что оба вышепредставленных интерфейса - находятся в Type Library. И надо реализовать то же самое, только теперь это будет транслироваться по сети и иметь автоматизацию.
Но у меня не получается уже который день. =(
Неужели получить такую картину невозможно? Почему-то мой автоматически создаваемый CoClass оказывается виртуальным (в нем не реализован метод AddRef, если я хочу сделать его экземпляром другого класса, но всё в порядке, если я через фабрику пытаюсь его создать клиентом).
В результате нужно получить аналог изложенного выше кода. И чтобы пользователь от сервера мог получить только IDouble.
Никакая агрегация не нужна, множественное наследование не нужно, всё это хорошо, пригодится, но для решения вот этой простой задачи (она наверняка простая) этого недостаточно.
Как же получить вот то же самое, только DCOM? Я же видел объекты КОМ чужие, они все устроены таким образом. Значит это возможно же.
Вот тут я создал проект, добавил в него 2 раза Automation Object, сделал интерфейсы IDouble и IResult, реализовал их (всё как в примере выше).
Но Result не сделал членом Double, потому что он почему-то абстрактный, хотя фабрика классов его реализовывает. Если кто-то может, с этим проектом это займет минуту, измените его так, чтобы Result можно было получить через Double, и нельзя было получить иначе. Там всё готово, надо сделать только эти действия.
Похоже на то, судя по его описанию, он реализует всё, что нужно.
Поскольку он не является шаблоном, я просто дописываю его так:
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<TResultImpl, &CLSID_Result>,
public IDispatchImpl<IResult, &IID_IResult, &LIBID_DoubleLib>,
public TAutoIntfObject
Так вот, это дает следующие ошибки.
[C++ Error] DESCRIPTIONIMPL.H(25): E2367 Can't inherit RTTI class from non-RTTI base 'TAutoIntfObject'
[C++ Error] DESCRIPTIONIMPL.H(31): E2251 Cannot find default constructor to initialize base class 'TAutoIntfObject'
Похоже этот класс не RTTI и не VCL (и почему-то это не дает ему быть предком).
У него есть только такой конструктор:
При этом _di_ITypeLib - это результат макроса, который как-то определяет тип интерфейса (DECLARE_DINTERFACE_TYPE(ITypeLib)), но как - не описано. Т.е. что туда передавать не понятно.
И вообще в описании указано, что это Дельфевый класс, отсюда, думаю, и все эти ошибки.
Это, похоже, то, что нужно, только всё равно такие объекты отказываются создаваться, т.к. содержат абстрактные методы...
В итоге тоже то, что я описал в 14м сообщении тут не построишь...