Устаревшая VB6 COM + DLL, вызывающая собственную Win32 DLL — проблемы с потоковой обработкой STA?

StackOverflow https://stackoverflow.com/questions/1057524

Вопрос

Сталкиваюсь с тем, что на первый взгляд выглядит как MT-проблема, но я пытаюсь детально разобраться в модели STA, используемой COM +.

Фактически, у меня есть устаревший компонент COM +, написанный на VB6, который вызывает собственную (то есть не-COM) библиотеку DLL Win32, написанную на C ++.

Столкнувшись с некоторыми прерывистыми (и невозможными для воспроизведения при тестировании) проблемами с ним, я добавил немного отладочного кода, чтобы выяснить, что происходит, и обнаружил, что при возникновении проблем у меня в файле чередовались сообщения журнала - так что это подразумевало, что DLL вызывалась двумя потоками одновременно.

Теперь протоколирование передается в файл для каждого потока на основе _getpid() и GetCurrentThreadId() , поэтому кажется, что при вызове кода в C ++ DLL он вызывается дважды в одном и том же потоке одновременно.Мое понимание STA говорит о том, что это может быть так, поскольку COM распределяет отдельные экземпляры объектов в один поток, приостанавливает и возобновляет выполнение по желанию.

К сожалению, я не уверен, куда идти дальше.Я читаю, что я должен вызывать CoInitialiseEx() в DllMain(), чтобы сообщить COM, что это STA DLL, но в других местах говорится, что это действительно только для COM DLL и не будет иметь никакого эффекта в родной DLL.Единственный другой вариант - обернуть части библиотеки DLL в критические разделы, чтобы сериализовать доступ (принимая на себя все возможные потери производительности).

Я мог бы попытаться переработать DLL, но нет общего состояния или глобальных переменных - все в локальных переменных, поэтому теоретически каждый вызов должен получать свой собственный стек, но мне интересно, оказывает ли модель STA в основном какой-то странный эффект на это и просто повторно входит в уже загруженную DLL в той же точке входа, что и другой вызов.К сожалению, я не знаю, как доказать или проверить эту теорию.

Вопросы в основном заключаются в следующем:

  1. Когда компонент STA COM + вызывает собственную библиотеку DLL, в модели STA нет ничего, что могло бы предотвратить приостановку активного "потока" и передачу управления другому "потоку" в середине вызова DLL?
  2. Является ли CoInitialiseEx() правильным способом решить эту проблему или нет?
  3. Если ни (1), ни (2) не являются "хорошими" предположениями, что происходит?
Это было полезно?

Решение

На многопоточном COM-сервере каждый экземпляр COM-класса гарантированно будет доступен одному потоку.Это означает экземпляр является потокобезопасным.Однако многие экземпляры могут быть созданы одновременно, используя разные потоки.Теперь, что касается COM-сервера, ваша собственная DLL не должна делать ничего особенного.Просто подумайте о kernel32.dll, который используется каждым исполняемым файлом - инициализирует ли он COM при использовании COM-сервером?

С точки зрения DLL, вы должны убедиться, что вы потокобезопасны, поскольку разные экземпляры могут вызывать вас одновременно.STA не будет защищать вас в этом случае.Поскольку вы говорите, что не используете никаких глобальных переменных, я могу только предположить, что проблема в другом месте, и просто случается, что проявляются обстоятельства, которые, кажется, указывают на COM-материал.Вы уверены, что у вас нет каких-нибудь старых проблем с памятью C ++?

Другие советы

Я подозреваю, что ваша проблема заключалась в том, что где-то глубоко внутри вызванной библиотеки DLL она выполнила исходящий COM-вызов в другую квартиру (другой поток в том же процессе, или объект в MTA, или полностью другой процесс).COM разрешает потоку STA, ожидающему результата исходящего вызова, принимать другой входящий вызов, обрабатывая его рекурсивно.Он предназначен только для постоянных разговоров между одними и теми же объектами , т. е.A вызывает B, B перезванивает A, A снова перезванивает B - но может принимать вызовы от других объектов, если вы передали указатель интерфейса нескольким клиентам или клиент передал указатель интерфейса другому клиенту.Как правило, это плохая идея передавать указатели интерфейса на однопоточный объект нескольким клиентским потокам, поскольку им придется только ждать друг друга.Создайте по одному рабочему объекту для каждого потока.

COM не может приостанавливать и возобновлять выполнение по желанию в любом потоке - новый входящий вызов в потоке STA может поступать только через перекачку сообщений.Когда "заблокирован" в ожидании ответа, поток STA фактически перекачивает сообщения, проверяя с помощью фильтра сообщений (см. IMessageFilter), следует ли обрабатывать сообщение.Однако обработчики сообщений не должны выполнять новый исходящий вызов - если они это сделают, COM вернет ошибку RPC_E_CANTCALLOUT_INEXTERNALCALL ("Вызов внутри фильтра сообщений запрещен").

Аналогичные проблемы могут возникнуть, если у вас есть перекачка сообщений (GetMessage / DispatchMessage) в любом месте встроенной библиотеки DLL.У меня были проблемы с DoEvents VB в процедурах интерфейса.

CoInitializeEx должен вызываться только создателем потока, потому что только он знает, каким будет его поведение при передаче сообщений.Вполне вероятно, что если вы попытаетесь вызвать его в DllMain, это просто завершится неудачей, поскольку ваша собственная DLL вызывается в ответ на вызов COM, поэтому вызывающий, в конечном счете, должен был уже вызвать CoInitializeEx в потоке, чтобы выполнить вызов.Выполнение этого в уведомлении DLL_THREAD_ATTACH для вновь созданных потоков может работать поверхностно, но привести к сбою программы, если COM блокируется, когда он должен выполнять перекачку, и наоборот.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top