Статья:
Шаблон проектирования “Одиночка” (Singleton) в ATL приложенияхАвтор(ы): Иван Андреев
Дата: 03.08.2003
Описание шаблона проектирования синглетон очень простое — синглетон представляет собой единственный экземпляр класса, с которым работают все клиенты. Применительно к COM шаблон проектирования синглетон гарантирует, что все вызовы CoCreateInstance будут возвращать указатель на интерфейс единственного экземпляра компонента. Удобство использования таких компонентов/классов заключается в том, что клиенты работают с одним и тем же экземпляром, а значит, получают доступ к разделяемому состоянию этого экземпляра. Несмотря на простое описание, не существует "идеальной" реализации этого шаблона ни в языке С++, ни для COM-объектов. Связано это с тем, что любая существующая реализация имеет некоторые ограничения и не может выступать в роли "универсальной" реализации на все случаи жизни.
Авторы:
Иван Андреев
Аннотация:
Описание шаблона проектирования синглетон очень простое — синглетон представляет собой единственный экземпляр класса, с которым работают все клиенты. Применительно к COM шаблон проектирования синглетон гарантирует, что все вызовы CoCreateInstance будут возвращать указатель на интерфейс единственного экземпляра компонента. Удобство использования таких компонентов/классов заключается в том, что клиенты работают с одним и тем же экземпляром, а значит, получают доступ к разделяемому состоянию этого экземпляра. Несмотря на простое описание, не существует "идеальной" реализации этого шаблона ни в языке С++, ни для COM-объектов. Связано это с тем, что любая существующая реализация имеет некоторые ограничения и не может выступать в роли "универсальной" реализации на все случаи жизни.
Здравствуйте, Иван Андреев, Вы писали:
ИА>Статья:
ИА>Авторы:
ИА> Иван Андреев
Отличная статья! Содержащийся в ней материал почти что полностью исчерпывает тему. Я говорю "почти" потому, что проблема маршаллинга так и осталась не решенной в случае использования STA. Но стоит ли ее решать при помощи маршаллинга?
Допустим, клиентский STA1 вызывает метод синглетона, инициализированного в STA2.
Давайте сравним следующие три способа реализации этого вызова:
1) Это делается через код Proxy/Stub, который использует SendMessage, чтобы синглетон выполнил этот метод в контексте STA2.
2) Proxy/Stub не используется, метод синглетона вызывается напрямую. Реализация метода использует крититическую секцию для разделения доступа из различных STA. Тело метода выполняется в контексте STA1.
3) Proxy/Stub не используется, метод синглетона вызывается напрямую. Реализация метода использует SendMessage, чтобы переключиться в контекст STA2. Основное тело метода выполняется в контексте STA2. Синглетон инкапсулирует STA2, и полностью контролирует время его жизни (такие объекты называют активными).
Если синглетон не контролирует время жизни STA2, то способ 1) принципиально неприменим. В противном случае лучше подходит способ 3) — если это не так, стоит всерьез задуматься о необходимости рефакторинга
.
Если же сиглетон не зависит от того, в каком STA он был инициализирован, то способ 2) подходит идеально.
Теперь о синглетоне-фениксе, а если точнее, то о проблеме потери сотояния синглетона после его разрушения при обнулении счетчика ссылок.
А кто сказал, что синглетон обязательно нужно разрушать? Не лучше ли вместо разрушения/создания использовать деактивацию/активацию? вот, например, участок кода, который написан с использованием библиотеки
ComXLib:
class MyServer:
public atl_com_object_root<>,
public CComCoClass<MyServer, &CLSID_MyServer>,
public IMyServer
{
private:
thread m_thread; // incapsulates STA
/* ...;
*/
// prefix "inside" means that function mus be called inside incapsulated STA
void insideActivate();
void insideDeactivate();
void insideDoSomething(SomeType1 arg1, SomeType2 arg2, SomeType3 arg3);
public:
/* ...;
*/
TX_DECLARE_ATL_SINGLETON(MyServer);
void on_reffed()
{
if(!m_thread.is_still_active())
m_thread.start();
m_thread.execute_member_0(this, MyServer::insideActivate);
}
void on_unreffed()
{
_ASSERTE(m_thread.is_still_active());
m_thread.execute_member_0(this, MyServer::insideDeactivate);
}
/* ...;
*/
STDMETHOD(DoSomething)(SomeType1 arg1, SomeType2 arg2, SomeType3 arg3)
{
itf_exception<IMyServer> ex;
TX_TRY_EX{
m_thread.execute_member_3(this, MyServer::insideDoSomething, arg1, arg2, arg3);
}TX_END_TRY_EX(ex);
return ex.push();
}
};
Думаю, профессионалам тут все будет ясно. Добавлю только, что в реализации метода IMyServer::DoSomething корректно осуществляется маршаллинг и обработка исключительных ситуаций.
Здравствуйте, Иван Андреев, Вы писали:
...
Попробовал сделать Singleton как Exe сервер:
class ATL_NO_VTABLE CObj :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CObj, &CLSID_Obj>,
public IConnectionPointContainerImpl<CObj>,
public IDispatchImpl<IObj, &IID_IObj, &LIBID_SINGLETONEXELib>,
public CProxy_IObjEvents< CObj >
{
DECLARE_CLASSFACTORY_SINGLETONMARSH(CObj);
public:
...
STDMETHOD (MakeEvent (IN BSTR bsName, IN BSTR bsParam, OUT long *pResult));
STDMETHOD (put_field (IN long newVal));
STDMETHOD (get_field (OUT long *pVal));
long m_lfield;
...
}
MakeEvent приводит к генерации события UsedElement (bsName, bsParam)
field — свойство, позволяющее читать и устанавливать внутреннюю переменную m_lfield
Не получается сделать 2 вещи:
1. Хочу одним скриптом (SetNum10.Js) установить значение внутренней переменной в 10, а другим скриптом (ReadNum.Js) прочитать это значение. Сейчас ReadNum.Js получает начальное значение, устанавливаемое в конструкторе CObj вне зависимости от вызова SetNum10.Js
Как побороть? Ведь это значит что объект все-таки не является синглтоном?
2. Хочу запустив N скриптов и вызвав метод MakeEvent в одном из них, получить событие UsedElement в каждом из скриптов. Как добиться такого? Сейчас событие получаю только в том скрипте, в котором вызван метод MakeEvent...
Заранее благодарен за ответы.
Здравствуйте, s0rc, Вы писали:
...
S>а не получается ли, что хоть он и синглтон, но умирает по окончанию скрипта, а новый скрипт работает с новым синглтоном? ведь никто не говорит, что синглтон есть обязательно (их <=1). поэтому сам сервер должен держать одну ссылку на все время работы (оформить в виде сервиса) (это догоро, но очень просто) или активация и деактивация (дешево и сердито с сохранением состояния).
Не думаю что так получается. первый скрипт запускает 2й, оба выводят MsgBox на котором и висят. 1й скрипт после нажатия на MsgBox'e генерит сообщение и опять идет на MsgBox (вижу и MsgBox и событие в первом скрипте). Во втором — только MsgBox.
Так работает Singleton в Dll вариант №1 (RSDN). Попробовал еще Singleton в Exe с фабрикой от MS (alternative...) — сообщения приходят в оба скрипта, но сообщения не приходят в html странице:
<html>
<body>
<body id=pagebody>
<script language="javascript">
function fnOnLoad ()
{
alert ("Loaded");
IEI.MakeEvent (42);
alert ("The End");
}
</SCRIPT>
<script for="IEI" event="UsedElement(ss1)" language="JavaScript">
alert("html. UsedElement("+ss1+")");
</script>
<body onLoad=fnOnLoad();>
<OBJECT id=IEI classid="clsid:E1F41D29-2AF1-4DE6-A9EE-E6A642DD2F2D"</OBJECT>
</body>
</html>
При том, что в скрипты сообщения приходят...