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

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

person Chris J    schedule 29.06.2009    source источник
comment
›› ему дважды звонят в одну и ту же цепочку одновременно. ‹< Сейчас это забавно, потому что это не может быть одновременно. Вероятно, этот код в лучшем случае вызывает себя рекурсивно.   -  person wqw    schedule 03.07.2009


Ответы (2)


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

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

person eran    schedule 29.06.2009
comment
Проблема возникает в производственной системе только во время загрузки. В коде DLL нет глобальных переменных, используются только локальные переменные. Выходной файл журнала, который имеет однозначное имя на основе идентификатора потока и процесса, содержит чередующиеся выходные данные, предполагая, что он дважды вызывается одним и тем же потоком в промежуточном состоянии. Во всех остальных средах код выполняется полностью правильно. - person THEMike; 29.06.2009
comment
1. Какие проблемы вы на самом деле испытываете? сбои, зависание, ненадлежащее поведение? 2. Создаете ли вы обсуждения или отправляете сообщения прямо или косвенно? 3. Попробуйте создать минидамп (или полный дамп), как только вы обнаружите проблему, трассировка стека может помочь определить проблему, не выходя из среды разработки. - person eran; 29.06.2009
comment
Повторите старые старые проблемы с памятью C ++: да, это было в конце концов, но не в области типов malloc (), а с массивом char, определенным как статический. Рэймонд Чен написал об этом здесь, blogs.msdn.com/oldnewthing/ archive / 2004/03/08 / 85901.aspx, и я нашел подтверждение на нескольких других сайтах. Похоже, что рефакторинг кода для удаления статики решил эту проблему. - person Chris J; 02.07.2009

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

person Mike Dimmick    schedule 02.03.2010