Устаревшая VB6 COM + DLL, вызывающая в родную Win32 DLL - проблемы с многопоточностью в STA? - PullRequest
7 голосов
/ 29 июня 2009

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

По сути, у меня есть устаревший компонент COM +, написанный на VB6, который вызывает собственную (т.е. не-COM) библиотеку Win32 DLL, написанную на 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) не являются «хорошими» предположениями, что происходит?

Ответы [ 2 ]

1 голос
/ 29 июня 2009

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

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

0 голосов
/ 02 марта 2010

Я подозреваю, что ваша проблема заключалась в том, что где-то глубоко в вызываемой 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 блокируется, когда она должна работать, и наоборот.

...