Вопрос очень простой: как правильно кидать исключения из методов COM-объектов? И что при этом происходит?
Более подробно:
Пишем программу на С++ под Win32 и VxWorks, внутри вовсю практикующую COM.
Виндовский порт использует MS ATL, VxWorks-овский — WindRiver ATL (жалкая пародия на MS).
Выхухоль не содержит ни CComException, ни _com_error, ни всего остального, поэтому придётся все детальки воссоздать. Хочу сделать это правильным способом.
Пока что проект сидит на inproc-серверах, которые имеют общий рантайм. Поэтому, кидая что попало (HRESULT, std::exception и т.п.) через границу метода, я не создам себе ошибок. А вот проблемы масштабирования программы (например, переход к DCOM) — обеспечу с лёгкостью.
Здравствуйте, Кодт, Вы писали:
К>Вопрос очень простой: как правильно кидать исключения из методов COM-объектов?
Никак
К>И что при этом происходит?
Все происходит плохо COM же с потоками, а C++ исключения работают только в одном потоке.
К>Пока что проект сидит на inproc-серверах, которые имеют общий рантайм. Поэтому, кидая что попало (HRESULT, std::exception и т.п.) через границу метода, я не создам себе ошибок.
Если есть маршалинг — создашь.
К>А вот проблемы масштабирования программы (например, переход к DCOM) — обеспечу с лёгкостью.
Да, это тем болеее.
По уму, из COM-метода ничего кроме HRESULT возвращать нельзя. А _com_exception это уже код на клиенте так обрабатывает возвращаемое значение метода. Ещё есть интерфейс IErrorInfo, но он актуален, если COM используется на VB, а больше вроде никем почти не используется.
Здравствуйте, Кодт, Вы писали:
К>Вопрос очень простой: как правильно кидать исключения из методов COM-объектов?
Ответ не менее прост: никак, нельзя кидать исключения за пределы исполняющегося СОМ метода. Т.е. СОМ объекту запрещено кидать исключения. Сообщение об ошибке идет через HRESULT каждого метода.
Здравствуйте, SergH, Вы писали:
К>>Пока что проект сидит на inproc-серверах, которые имеют общий рантайм. Поэтому, кидая что попало (HRESULT, std::exception и т.п.) через границу метода, я не создам себе ошибок.
SH>Если есть маршалинг — создашь.
Да, забыл сказать, что free-threaded. Поэтому маршалинга нет.
К>>А вот проблемы масштабирования программы (например, переход к DCOM) — обеспечу с лёгкостью.
SH>Да, это тем болеее.
SH>По уму, из COM-метода ничего кроме HRESULT возвращать нельзя. А _com_exception это уже код на клиенте так обрабатывает возвращаемое значение метода. Ещё есть интерфейс IErrorInfo, но он актуален, если COM используется на VB, а больше вроде никем почти не используется.
Я думал, есть какой-то штатный способ маршалить ошибки. В MSDN видел трёп про CreateErrorInfo и т.д., но понял, что это относится к IDispatch.
Ну если нет, то нет
(Буду втихую пользоваться исключениями, а потом избавлюсь от COM всюду где только возможно).
Здравствуйте, Кодт, Вы писали:
К>Вопрос очень простой: как правильно кидать исключения из методов COM-объектов? И что при этом происходит?
К>Более подробно: К>Пишем программу на С++ под Win32 и VxWorks, внутри вовсю практикующую COM. К>Виндовский порт использует MS ATL, VxWorks-овский — WindRiver ATL (жалкая пародия на MS). К>Выхухоль не содержит ни CComException, ни _com_error, ни всего остального, поэтому придётся все детальки воссоздать. Хочу сделать это правильным способом.
К>Пока что проект сидит на inproc-серверах, которые имеют общий рантайм. Поэтому, кидая что попало (HRESULT, std::exception и т.п.) через границу метода, я не создам себе ошибок. А вот проблемы масштабирования программы (например, переход к DCOM) — обеспечу с лёгкостью.
Простите за нескромный вопрос. Почему вообще возникла необходимость писать под VxWorks с использованием COM?
Старый Торнадо на котором мне приходилось работать, врядли бы потянул С++ для еще с STL/ATL/COM, как быстро время бежит... эх... Неужто он так сильно продвинулся?
Здравствуйте, Кодт, Вы писали:
К>Я думал, есть какой-то штатный способ маршалить ошибки. В MSDN видел трёп про CreateErrorInfo и т.д., но понял, что это относится к IDispatch.
Относится только тем, что IErrorInfo наследуется от IDispatch. Больше ничем .
На самом деле все достаточно удобно и прозрачно.
При возникновении ошибки на сервере:
* создаем системный объект, реализующий IErrorInfo и ICreateErrorInfo, вызовом CreateErrorInfo()
* устанавливаем его свойства через IErrorInfo::SetDescription() и т.п.
* получаем у этого объекта IErrorInfo
* устанавливаем его в качестве текущего объекта ошибки с помощью SetErrorInfo и возвращаем HRESULT ошибки. Указатель на текущий объекто ошибки системный и thread-local'ьный, поэтому даже не напрягаемся на счет синхронизации.
(Для проведения вышеиложенного в ATL есть грубенькая, совершенно не гибкая функция ::AtlReportError()).
А в клиенте проверяем возвращенный HRESULT и тупо-глупо получаем устанавленный объект через GetErrorInfo и читаем GetDescription и т.п.
Если объект ошибки создается через CreateErrorInfo, то о маршалинге беспокоиться не надо — все уже есть.
Есть еще опциональные мистические хитрости, связанные с ISupportErrorInfo, но это на любителя, так как даже если кокласс не реализует ISupportErrorInfo, то тот же VB6 прекрасно возвращает информацию об установленной ошибке в своем Err и удаленно информация тоже нормально передается в этом случае.
Основная задача, которую приходиться решать, если все делать грамотно — это протаскивание информации об ошибке вверх по стеку, прописывая по пути информацию о контексте (ala .net Exception.InnerException), на это есть (как минимум :) два подхода.
Либо реализовывать собственный объект ошибки, то есть не вызывать CreateErrorInfo вовсе, а в SetErrorInfo указывать IErrorInfo своего. Собственный объект по мимо IErrorInfo может реализовывать еще что-то (в этом и суть), например Chained Error Info for COM, но в этом случае, конечно, маршалинг тоже весь свой.
Либо в качестве Description передавать не просто описание ошибки, а некоторую текстовую структуру, например xml (это не слишком дружелюбно по отношению к конечному пользователю, зато дружелюбно по отношению к разработчику), где дописывать контекстную информацию (очевдно, что хотя бы какая-то часть стека должна знать и использовать формат строк), например как предлагается в Propagate Error Info: Use ATL and C++ to Implement Error-Handling COM Objects, или, если клиенты будут на VB6, то можно использовать простой формат HuntErr, он не xml, но тоже вполне удобный, в особенности для конечного пользователя (я как-то использовал этот вариант).
К>(Буду втихую пользоваться исключениями, а потом избавлюсь от COM всюду где только возможно).
Совершенно напрасно — на IErrorInfo легко построить передачу ошибок и внутри сервера, то есть обойтись без исключений, но не упираться в возвращаемые значения — зато потом из внутреннего объекта сервера можно будет сделать публичный и все клиенты сразу поймут как принимать от него информацию об ошибках.
P.S.
О скорости сказать ничего не могу. Такой проблемы когда я это делал у меня не стояло, но по ощушениям, вроде не медленнее кидания исключений (но не быстрее возвращения значений ;).
Здравствуйте, ssi, Вы писали:
ssi>Простите за нескромный вопрос. Почему вообще возникла необходимость писать под VxWorks с использованием COM? ssi>Старый Торнадо на котором мне приходилось работать, врядли бы потянул С++ для еще с STL/ATL/COM, как быстро время бежит... эх... Неужто он так сильно продвинулся?
Не знаю, с какого момента он продвинулся. У нас далеко не самое свежее.
Торнадо II, конечно, редкий тормоз в роли среды кросс-разработки (поэтому насколько возможно, пользуемся VC6).
А на таргете-то — какая разница, тормоз он или нет. Быстрый проц (пень-два или пень-три) — и всё летает.
Медляк возникает при "честной" передаче строк и массивов как out-параметров, но, как выяснило профилирование, это не самое узкое место. Хотя, конечно, если от COM взять только RTTI и сборку мусора — то всё ускорится...
А потребность возникла, потому что хотели делать сеть контроллеров с внешним API — DCOM/RPC (возможно, SOAP). Потом передумали, но было уже поздно
Здравствуйте, Frostbitten, Вы писали:
К>>(Буду втихую пользоваться исключениями, а потом избавлюсь от COM всюду где только возможно). F>Совершенно напрасно — на IErrorInfo легко построить передачу ошибок и внутри сервера, то есть обойтись без исключений, но не упираться в возвращаемые значения — зато потом из внутреннего объекта сервера можно будет сделать публичный и все клиенты сразу поймут как принимать от него информацию об ошибках.
F>P.S. F>О скорости сказать ничего не могу. Такой проблемы когда я это делал у меня не стояло, но по ощушениям, вроде не медленнее кидания исключений (но не быстрее возвращения значений ;).
Просто я думал, что можно бы сделать так.
Пусть есть некий "совместимый" класс исключения, скажем, ComEx.
Если метод бросает исключение, то, пока мы в пределах апартамента, стек раскручивается как обычно. Поймали ниже? Отлично. Не поймали?
— в функции потока есть ловушка catch(...) { много матов и abort(); }
— в прокси/стабе так же есть ловушка
// прокси
HRESULT IXxxx_Foo_Proxy(IXxxx* pXxxx, аргументы)
{
HRESULT hr;
... // замаршалить, вызвать и размаршалить обратноif(hr == EXCEPTION_E_COMEX) throw ComEx_From_ErrorInfo();
if(hr == EXCEPTION_E_OTHER) throw bad_exception("Some unknown exception thrown through the apartment");
return hr;
}
// стабvoid IXxxx_Foo_Stub(/* вся ботва, которая идёт в стаб */)
{
CComPtr<IXxx> pXxx;
HRESULT hr;
... // размаршалитьtry
{
HRESULT hr = pXxx->Foo(.......);
}
catch(ComEx ex)
{
SetErrorInfo_From_ComEx(ex);
hr = EXCEPTION_E_COMEX;
}
catch(...)
{
hr = EXCEPTION_E_OTHER;
}
... // замаршалить обратно
}
MIDL, кстати сказать, засовывает нечто подобное в TheLibrary_ps.c, но ориентированное на SEH (и это можно понять: proxy/stub — это сишная программа, а не С++ная). VxIDL про SEH ничего не знает, поэтому даже не пытается.
Здравствуйте, Frostbitten, Вы писали:
К>>Я думал, есть какой-то штатный способ маршалить ошибки. В MSDN видел трёп про CreateErrorInfo и т.д., но понял, что это относится к IDispatch. F>Относится только тем, что IErrorInfo наследуется от IDispatch. Больше ничем .
Увы, это ошибочно: IErrorInfo НЕ НАСЛЕДУЕТСЯ от IDispatch.
Он, IErrorInfo, обслуживает Автоматизацию (заполнение структуры EXCEPINFO) и, следовательно, связан с IDispatch-интерфейсами. Вернее, именно с dual-ными интерфейсами: только через дополнительный интерфейс/объект можно было передать информацию об ошибке из виртуального метода.
Здравствуйте, Vi2, Вы писали:
F>>Относится только тем, что IErrorInfo наследуется от IDispatch. Больше ничем . Vi2>Увы, это ошибочно: IErrorInfo НЕ НАСЛЕДУЕТСЯ от IDispatch.
Оу, подтверждаю, просто я смотрел в MSDNL Oct 2001 (для 6-ки), там недвусмыслено перечислены IDispatch'евые методы в секции "Methods in Vtable Order", что действительно не соответствует реальности. Сейчас посмотрел msdn.microsoft.com — там исправили.