SEHException при вызове метода COM-объекта из другой нити
От: apilikov Россия  
Дата: 06.10.10 11:30
Оценка:
Уважаемые коллеги!

есть довольно неприятная проблема.
есть COM компонент который используется .NET клиентом. Данный компонент оформлен в виде DLL. Назовем его ядром. Внутри себя он вызывает функции другого COM-компонента, который в свою очередь оформлен в виде EXE. Назовем его сервером.

ход выполнения программы выглядит примерно так:
программа на .NET использующая Windows Forms имеет главное окно которое выглядит так:

namespace WindowsFormsApplication9
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public IMyApp m_app2;

        private void button1_Click(object sender, EventArgs e)
        {

            IApp app = new AppClass();
            m_app2 = (IMyApp)app.GetApp();

            Form2 frm2 = new Form2(m_app2);

            frm2.ShowDialog();
        }
    }
}


как прекрасно видно форма эта по нажатию на кнопку создает другую форму которая выглядит так:

namespace WindowsFormsApplication9
{
    public partial class Form2 : Form
    {
        public IMyApp m_app2;

        public Thread m_thread;

        public Form2(IMyApp app2)
        {
            m_app2 = app2;

            m_thread = new Thread(TreadEntryPoint);
            m_thread.Start();

            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
        }

        private void TreadEntryPoint()
        {
                     m_app2.SomeMethod("test");
        }
    }
}


как прекрасно видно форма в своем конструкторе получает указатель на некий интерфейс COM-объекта созданного в основном потоке, потом создает новый поток и вызывает в нем функцию этого объекта.
При вызове функции SomeMethod вызов попадает сначала в ядро, а потом переадресуется внешнему компоненту реализованному в EXE (так называемому серверу). Так вот собственно теперь проблема.
Если во время выполнения SomeMethod я идентифицирую ошибку через SetErrorInfo, то все прекрасно. Во второй нити я получаю COMException в котором вижу корректные данные об ошибке, здесь все нормально.
Однако стоит мне сделать тоже самое в сервере и получить эту ошибку в ядре, то независимо от того, игнорирую ее я или передаю уже непосредственно в .NET поток создавший объект (внимание, не тот который вызвал SomeMethod) получается SEHException c описанием "Внешний компонент создал исключение".

Пока категорически не могу понять почему это происходит именно так и как с этим бороться. Буду признателен любым мыслям которые у вас есть. Заранее благодарю.
Re: SEHException при вызове метода COM-объекта из другой нит
От: Jolly Roger  
Дата: 08.10.10 03:43
Оценка:
Здравствуйте, apilikov, Вы писали:

Коротко говоря, Вы нарушаете требования апартаментной модели COM. Вы создаёте "ядро" в STA апартаменте главного потока, а вызываете метод в другом потоке, входящем в другой апартамент. При таком подходе проблемы с SetErrorInfo — не единственная, которая может Вас ожидать. Простейшее решение — создавать объект "ядра" в доп. потоке — члене MTA и использовать его только из потоков — членов MTA.

PS Постарайтесь всё-таки расставлять запятые, иначе Ваш текст тяжело читать.
"Нормальные герои всегда идут в обход!"
Re[2]: SEHException при вызове метода COM-объекта из другой
От: Аноним  
Дата: 08.10.10 08:35
Оценка:
Здравствуйте, Jolly Roger, Вы писали:

JR>Здравствуйте, apilikov, Вы писали:


JR>Коротко говоря, Вы нарушаете требования апартаментной модели COM. Вы создаёте "ядро" в STA апартаменте главного потока, а вызываете метод в другом потоке, входящем в другой апартамент. При таком подходе проблемы с SetErrorInfo — не единственная, которая может Вас ожидать. Простейшее решение — создавать объект "ядра" в доп. потоке — члене MTA и использовать его только из потоков — членов MTA.


JR>PS Постарайтесь всё-таки расставлять запятые, иначе Ваш текст тяжело читать.


насчет запятых постараюсь учесть.

что касается вашего поста, то не очень понятно что именно я нарушаю.

когда я передаю указатель на COM объект в другую нить, то при вызове из другой нити вызов пойдет через прокси, т.е. будет выполнен маршаллинг и код ядра все равно будет исполнен в том потоке который создал объект. Так работает COM. И в этом состоит смысл STA который у меня используется.

И я кстати разобрался с причиной появления у меня SEHException и сейчас заодно изложу вам в чем же там было дело.

Суть в том что из "ядра" я вызывал функцию некоторой DLL-ки. Это просто WIN32 библиотека с экспортируемыми функциями в стиле C.
Из этой DLL-ки собственно и вызывалась функция "сервера". Я опустил этот момент когда описывал проблему потому что посчитал ее несущественной. Теперь же я понимаю что этот момент был ключевым, приношу извинения.
Фишка в том что когда я возбуждал ошибку в "сервере" она приходила в DLL в виде _com_error, но я не ловил ее в DLL-ке а передавал сразу в "ядро" где у меня был написан соответствующий catch. Так вот в Windows есть такая фигня как контекст активации. И у этих контекстов есть определенный порядок. Так вот если из DLL-ки выбросить исключение за ее пределы, то в catch который находится в ядре будет словлено исключение. Все вроде бы хорошо, однако после того как COM-вызов будет завершен и я выйду из кода "ядра" Windows захочет деактивировать контекст активации и учитывая что я грубо вышел из кода DLL-ки деактивируемый контекст активации не будет являться последним активированным контекстом. Есть даже специальное WIN32 исключение которое именно так и называется и имеет код 0xC0150010.
Так вот при выходе из кода "ядра" Windows генерировала это исключение, которое в свою очередь .NET не знала как смапить, ну т.е. на какое исключение из ее набора, смапить это исключение и в итоге мапила его на SEHException.
После того как я сделал обработку исключений непосредственно в DLL все сразу заработало корректно.
Вызовы из другой нити прекрасно проходят в ту нить которая создала объект, потом я вбрасываю исключение и корректно получаю его во второй нити.
Так что вот такая вещь, век живи век учись.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.