Здравствуйте, Vi2, Вы писали:
Vi2>Здравствуйте, SkySniper, Вы писали:
SS>>Потом понял что при вызове методов с клиента сервер не знает какой именно клиент вызвал метод и понял что архитектуру придется здорово поменять, в принципе это не проблема, можно хоть заново все переписать.
Vi2>Дело в том, что в СОМе клиент не обладает или может не обладать интерфейсами
"может не обладать" --- а может и обладать . Посмотри на тему, которая называется disp-interfaces. С помощью этого можно заставить сервер вызывать методы интерфейса клиента. С этим надо быть осторожным, ибо зависание клиента в этом случае может привести к зависанию сервера (а сервер вроде должен быть безотказным ) ну там и прочая серверная лабуда, но уж если ты пишешь такой сервер, наверняка о них должен знать
По поводу идентификации клиента: можно немного усложнить архитектуру. Например, реализовать сервер в виде 2 частей: основной и представительской. Клиент должен создать себе представителя. Представитель содержит в себе какую-либо уникальную информацию, чтобы клиента по ней можно было идентифицировать. Самое простое: порядковый номер соединения, каждый раз увеличивающийся на 1). Клиент вызывает методы у представителя, который перенаправляет их основной части, добавляя эту уникальную информацию.
Может можно придумать что еще. Кстати, интересная задача, отпишись что у тебя получится итоге)))
, главы "VARIANT изнутри" и "VARIANT и структуры" или вся статья целиком. Там ответы на все вопросы. Если и после этого что-то останется непонятным, то милости просим снова сюда.
Всем здравствуйте.
COM только начинаю изучать, так что прошу сильно не бить.
Имеется 2 приложения: сервер(сервис) и клиент, общающиеся через TCP.
class CServer
{
...
CDeviceManager m_DeviceManager;
std::vector<CClient*> m_Clients;
...
}
class CDeviceManager
{
...
std::vector<CDevice*> m_Devices;
...
}
Алгоритм сервера, в общих чертах, такой:
1. При запуске, поиск неких подключенных устройств, заполнение m_Devices.
2. При подключении/отключении нового клиента соответственно заполнение m_Clients.
3. При получении команды от клиента вызов void CServer::processClientCommand(CClient* pClient).
Использую Visual Studio 2008, ATL.
Подскажите как можно построить архитектуру сервера используя COM?
Поиск по интернету практически ничего не дал кроме каши в голове .
Начал эксперименты с чего то примерно такого :
class ATL_NO_VTABLE CServer :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CServer, &CLSID_Server>,
public IConnectionPointContainerImpl<CServer>,
public CProxy_IServerEvents<CServer>,
public IDispatchImpl<IServer, &IID_IServer, &LIBID_VideoServerLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
DECLARE_CLASSFACTORY_SINGLETON(CServer);
Потом понял что при вызове методов с клиента сервер не знает какой именно клиент вызвал метод и понял что архитектуру придется здорово поменять, в принципе это не проблема, можно хоть заново все переписать.
Есть кое какие мысли, но не уверен в их правильности.
Например использовать что то типа этого при подключении клиента:
class ATL_NO_VTABLE CClient :
public CComObjectRootEx<CComMultiThreadModel>,
...
{
public:
DECLARE_CLASSFACTORY_AUTO_THREAD();
В этом случае при каждом подключении будет создаваться новый экземпляр клиента, но не знаю как это увязать с сервером.
Здравствуйте, SkySniper, Вы писали:
SS>Потом понял что при вызове методов с клиента сервер не знает какой именно клиент вызвал метод и понял что архитектуру придется здорово поменять, в принципе это не проблема, можно хоть заново все переписать.
Дело в том, что в СОМе клиент не обладает или может не обладать интерфейсами, в то время как сервер обязан. Поэтому, если ты мыслишь в терминах интерфейсов (ну, классов), при вызове метода сервера клиент может передавать интерфейс, который его характеризует или по которому его можно оповещать. Кстати, можно задействовать тот же IServerEvents интерфейс, который клиент передал бы для подписки на события сервера.
Передавать клиентом что то при вызовом каждого метода сервера для идентификации клиента мне тоже в голову приходило, но мне кжется это не лучшее решение. Я тут еще подумал что устройства тоже хорошо бы создавать как COM объекты и передавать их клиенту, если это возможно, т.к. тогда автоматом получаем возможность оповещать о событиях на конкретных устройствах, конкретных клиентов, имеющих ссылки на устройства.
Вопрос в том как это реализовать в коде?
Здравствуйте, SkySniper, Вы писали:
SS>Я тут еще подумал что устройства тоже хорошо бы создавать как COM объекты и передавать их клиенту, если это возможно, т.к. тогда автоматом получаем возможность оповещать о событиях на конкретных устройствах, конкретных клиентов, имеющих ссылки на устройства. SS>Вопрос в том как это реализовать в коде?
Dim WithEvents oServer As Server
Dim WithEvents oDevice As Device
Sub Main ' или Form_Load()Set oServer = GetObject(,"Server_ProgID") ' или CreateObject("Server_ProgID")Set oDevice = oServer.Device("параметры устройству")
oDevice.DoSomething ...
End Sub' методы интерфейса IServerEventsSub oServer_Exit()
...
End Sub' методы интерфейса IDeviceEventsSub oDevice_DataRecieved()
...
End Sub
С созданием и подпиской на события я худо бедно разобрался, интересует созадние самих объектов на сервере (например тех же устройств) и передача их клиенту. Т.е. как я понимаю нужно будет испольховать фабрику классов примерно такую как при SINGLETON.
SS>Алгоритм сервера, в общих чертах, такой: SS>1. При запуске, поиск неких подключенных устройств, заполнение m_Devices. SS>2. При подключении/отключении нового клиента соответственно заполнение m_Clients. SS>3. При получении команды от клиента вызов void CServer::processClientCommand(CClient* pClient).
Что то я все никак продвинуться не могу, теперь постараюсь задавать более конкретные вопросы.
Допустим класс устройства выглядит так:
class ATL_NO_VTABLE CDevice :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CDevice, &CLSID_Device>,
public IConnectionPointContainerImpl<CDevice>,
public CProxy_IDeviceEvents<CDevice>,
public IDispatchImpl<IDevice, &IID_IDevice, &LIBID_VideoServer2Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CDevice()
{
}
DECLARE_CLASSFACTORY_SINGLETON(CDevice);//DECLARE_REGISTRY_RESOURCEID(IDR_DEVICE)DECLARE_NO_REGISTRY()
BEGIN_COM_MAP(CDevice)
COM_INTERFACE_ENTRY(IDevice)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CDevice)
CONNECTION_POINT_ENTRY(__uuidof(_IDeviceEvents))
END_CONNECTION_POINT_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
private:
//тут методы и поля данных интерфейса
...
public:
//тут методы и поля данных интерфейса
...
};
//OBJECT_ENTRY_AUTO(__uuidof(Device), CDevice)OBJECT_ENTRY_NON_CREATEABLE_EX_AUTO(__uuidof(Device), CDevice)
Класс сервера, при запуске должен будет искать устройства и занести их в m_Devices:
class ATL_NO_VTABLE CServer :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CServer, &CLSID_Server>,
public IConnectionPointContainerImpl<CServer>,
public CProxy_IServerEvents<CServer>,
public IDispatchImpl<IServer, &IID_IServer, &LIBID_VideoServer2Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
CServer()
{
}
DECLARE_CLASSFACTORY_SINGLETON(CServer);
DECLARE_REGISTRY_RESOURCEID(IDR_SERVER)
BEGIN_COM_MAP(CServer)
COM_INTERFACE_ENTRY(IServer)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CServer)
CONNECTION_POINT_ENTRY(__uuidof(_IServerEvents))
END_CONNECTION_POINT_MAP()
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return S_OK;
}
void FinalRelease()
{
}
private:
std::vector<CDevice*> m_Devices;size_t CServer::AddDevice()
{
[b]//как тут создать устройство?
m_Devices.push_back(device);
return m_Devices.size();
}[/b]
private:
//тут методы и поля данных интерфейса
...
public:
//тут методы и поля данных интерфейса
...
};
OBJECT_ENTRY_AUTO(__uuidof(Server), CServer)
А как теперь передать клиенту в другом процессе интерфейс IDevice одного из устройств из std::vector<CDevice*> m_Devices.
Что то типа:
HRESULT CServer::get_Device(IDevice* *dev)
{ //что тут?
//return m_Devices[0]->QueryInterface(__uuidof(IDevice), (void**)dev); //это нужно как то отмаршалить?
}
Здравствуйте, SkySniper, Вы писали:
SS>А как теперь передать клиенту в другом процессе интерфейс IDevice одного из устройств из std::vector<CDevice*> m_Devices. SS>Что то типа: SS>HRESULT CServer::get_Device(IDevice* *dev) SS>{ SS> //что тут? SS> //return m_Devices[0]->QueryInterface(__uuidof(IDevice), (void**)dev); //это нужно как то отмаршалить? SS>}
Если ты создаешь ATL exe server — у тебя должно создаваться 2 проекта в сборке(правильно я solution перевел?). один из них proxy-stub dll. он генерируется автоматически на основе библиотеки типов(то, что в *.idl файле в основном проекте).
В этом случае о маршаллинге можно забыть — он происходит без участия программиста. т.е. твой код корректен.
иначе — не пробовал, не знаю
Еще вопрос. В книге "Inside COM" написано: "интерфейс совместимый с Автоматизацией наследует IDispatch и использует только такие типы параметров, которые можно поместить в VARIANT. Для таких типов OLEAUT32.DLL выполняет маршалинг автоматически.".
Что делать если например необходимо передавать параметр типа RECT? Не очень то хочется передавать 4 параметра вместо 1го, даже в таком простом случае, не говоря уже о более сложных структурах.
Здравствуйте, SkySniper, Вы писали:
SS>Еще вопрос. В книге "Inside COM" написано: "интерфейс совместимый с Автоматизацией наследует IDispatch и использует только такие типы параметров, которые можно поместить в VARIANT. Для таких типов OLEAUT32.DLL выполняет маршалинг автоматически.". SS>Что делать если например необходимо передавать параметр типа RECT? Не очень то хочется передавать 4 параметра вместо 1го, даже в таком простом случае, не говоря уже о более сложных структурах.
Описываешь структуру и передаешь ее — структура также является "совместимой с Автоматизацией" и имеет тип VT_RECORD.
Здравствуйте, Vi2, Вы писали:
Vi2>Здравствуйте, SkySniper, Вы писали:
SS>>Еще вопрос. В книге "Inside COM" написано: "интерфейс совместимый с Автоматизацией наследует IDispatch и использует только такие типы параметров, которые можно поместить в VARIANT. Для таких типов OLEAUT32.DLL выполняет маршалинг автоматически.". SS>>Что делать если например необходимо передавать параметр типа RECT? Не очень то хочется передавать 4 параметра вместо 1го, даже в таком простом случае, не говоря уже о более сложных структурах.
Vi2>Описываешь структуру и передаешь ее — структура также является "совместимой с Автоматизацией" и имеет тип VT_RECORD.
А можно чуть подробнее?
У меня сервер на C++, клиент на Delphi.
Запись в IDL файле Visual Studio:
[id(3), helpstring("method ResizeWindow")] HRESULT ResizeWindow([in] LONG channel, [in] RECT inRect, [in] RECT outRect);
Определение прототипа:
STDMETHOD(ResizeWindow)(LONG channel, RECT inRect, RECT outRect);
Помогите понять, следующий вызов работает, достаточно лишь зарегистрировать библиотеку типов.
procedure TForm1.Panel1Resize(Sender: TObject);
var
rect1: tagRECT;
begin
if (DeviceObj<>nil) then begin
rect1 := tagRect(Panel1.BoundsRect);
DeviceObj.ResizeWindow(1, rect1, rect1);
end;
end;
Следовательно он использует proxy/stub DLL автоматизации(Ole32.dll), и мне нет необходимости создавать регистрировать свой proxy/stub?
Но смущает меня вот этот код, насколько я понимаю он не верен, в каких случаях будет использоваться dispinterface?
Здравствуйте, SkySniper, Вы писали:
SS>Следовательно он использует proxy/stub DLL автоматизации(Ole32.dll), и мне нет необходимости создавать регистрировать свой proxy/stub? SS>Но смущает меня вот этот код, насколько я понимаю он не верен, в каких случаях будет использоваться dispinterface?
Для вопросов по Дельфи есть специальный форум. Здесь же мы можем слегка подтолкнуть в нужном направлении. IDevice, похоже, наследует от IDispatch и является дуальным (dual) интерфейсом, т.е. состоит как бы из двух интерфейсов — диспинтерфейса (dispinterface) и обычного интерфейса (custom). Они отличаются способом вызова методов, другое прочтение этого обстоятельства — позднее и раннее связывание.
Поскольку IDevice наследует от IDispatch, то СОМ предоставляет средства для создания proxy/stub, которым для работы нужна зарегистрированная библиотека типов TLB. При ее отсутствии нужно предоставить свою версию proxy/stub DLL.
Почему Дельфи меняет тип параметра — одному Дельфи известно (еще, пожалуй, дельфистам). Видимо так нужно в языке.
SS>Будет ли верным решением использовать подобный код вызова методов DeviceObj.ResizeWindow(1, rect1, rect1)?
Это как Дельфи позволит. VB, например, позволит такой синтаксис и для IDevice, и для IDeviceDisp.
Структура RECT упаковывается в VARIANT, т.к. она описана в IDL при описании метода, и, следовательно, предоставляет всю информацию для упаковки ее в VARIANT.
Здравствуйте, Vi2, Вы писали:
Vi2>Структура RECT упаковывается в VARIANT, т.к. она описана в IDL при описании метода, и, следовательно, предоставляет всю информацию для упаковки ее в VARIANT.
Боюсь, не совсем Вас понял, нельзя-ли чуть подробней?
Ведь dispinterface — сущность времени компиляции, предназначенная для ID-binding'а, в run-time за ней обычный IDispatch, в вызов Invoke которого компилятор преобразует вызовы методов диспинтерфейса. В Invoke-же параметры передаются через массив VARIANT'ов, один параметр — один VARIANT. Мы можем каждое из полей RECT поместить в отдельный VARIANT, но целиком в один — нет.
Совместимость с OLE означает, что информация о способе маршалинга каждого из полей RECT "вшита" в универсальный маршалер, а библиотека типов предоставляет информацию о расположении полей. При этом не обязательно даже интерфейс наследовать от IDispatch, вполне подойдёт IUnknown. В результате универсальный маршалер имеет возможность маршалировать и демаршалировать RECT как единый параметр, при этом VARIANT как таковой не нужен и не используется. Так оно работает при early binding'е.
Но ID binding и late binding — дело другое. Первый от второго отличается только тем, что во время компиляции все ID уже известны, и нет нужды их запрашивать в ран-тайме. При этом в ран-тайме библиотека типов не нужна и не используется, информации о структуре RECT у универсального маршалера нет, а упаковать её в один VARIANT невозможно из-за отсутствия соответствующей, известной маршалеру константы VT_RECT. А работа через dispinterface — это и есть ID binding.
ИМХО, единственный способ передать RECT при late or ID binding'е — SafeArray. Ну или дополнитедьный метод, в котором каждое из полей будет представлено отдельным параметром.
Всем привет, снова вопросы
Имеется SINGLETON сервер, к нему подключаются клиенты.
Каким образом передавать информацию от сервера конкретному клиенту?
Ведь при использовании ConnectionPoint сервер будет оповещать всех подключенных к нему клиентов.
Может создавать COM объект в клиенте передавать его интерфейс серверу и вызывать его методы? Но не придется ли при этом настраивать доступ и для клиентской машины?
Может еще что то посоветуете?
Здравствуйте, SkySniper, Вы писали:
SS>Всем привет, снова вопросы SS>Имеется SINGLETON сервер, к нему подключаются клиенты. SS>Каким образом передавать информацию от сервера конкретному клиенту? SS>Ведь при использовании ConnectionPoint сервер будет оповещать всех подключенных к нему клиентов.
Можно и конкретных клиентов. Реализация ведь генерируется мастером(ну в ATL по крайней мере). И оповещение происходит следующим образом: в цикле у каждого клиента вызывается соответствующий метод. На этом этапе можно как-то фильтровать эти вызовы.
SS>Может создавать COM объект в клиенте передавать его интерфейс серверу и вызывать его методы? Но не придется ли при этом настраивать доступ и для клиентской машины?
Ну метод Connection Point делает то же самое, только через IDispatch. т.е. Если у вас есть Connection Point, значит на клиенте у вас COM классы. И раз настраивать доступ не пришлось — то и с добавлением новых интерфейсов у клиентских классов проблем быть не должно.
Но с DCOM(я так понимаю — у вас сервер через сеть) не сталкивался — последняя реплика только логический вывод.