Проблемы вызова интерфейса IConnectionPointImpl из C++, вызываемого через модальные WinForms
У нас есть родное приложение C++, которое поддерживает некоторые макросы VBA различных типов через COM. Один из этих типов, VBAExtension
, регистрируется в основном приложении C++, в результате чего создается экземпляр класса (производного от) IConnectionPointImpl<Extension, &DIID_IExtensionEvents, CComDynamicUnkArray>
, Это отлично работает; и основные, и другие макросы VBA могут обращаться к методам IExtensionEvents, если у них есть соответствующий объект VBAExtension.
У нас также есть сборка.NET (написанная на C#), которая также загружается в основное приложение во время выполнения. По историческим причинам сборка загружается автоматически запускаемым макросом VBA; затем, когда пользователь нажимает определенную кнопку, другой макрос VBA запускает основную точку входа в сборку, которая вызывает System.Windows.Forms
диалог для дальнейшего взаимодействия.
Это настройка. Я вижу странное поведение при доступе к VBAExtension
методы из сборки.NET. В частности, я запускаю следующий код из разных мест сборки:
foreach (VBAExtension ve in app.Extensions)
{
System.Diagnostics.Debug.Print("Ext: " + ve.Name);
}
Если я запускаю его из конструктора основного объекта сборки; или из главной точки входа в сборку (до отображения диалогового окна) все в порядке - я получаю имена VBAExtension
распечатано.
Однако, если я запускаю тот же код из команды, запускаемой кнопкой в сборке (модально - мы вызываем form.ShowDialog()
) WinForm ve.Name
все пустые. pDispatch->Invoke
звонок сделан IConnectionPointImpl
подкласс успешен (возвращает S_OK), но не устанавливает никаких возвращаемых переменных.
Если я изменю диалог на немодальный (вызывается с form.Show()
), то имена снова работают. Модальность (модальность?) Формы, кажется, влияет на то, IConnectionPointImpl
звонки успешны.
Кто-нибудь знает, что происходит?
Изменить: Со времени первой публикации я продемонстрировал, что важен не стек вызовов; вместо этого, будет ли вызов сделан из модального диалога. Я обновил основной текст.
Редактировать 2: Ответ Ханса Пассанта, вот ответы на его диагностические вопросы:
- Как и ожидалось, в хорошем (немодальном) случае нет ошибки, если я переименую обработчик событий VBA. Вызов просто не возвращает данных.
- Я поместил вызов MsgBox в обработчик VBA; он отображается в немодальном случае, но не в модальном. Ergo, обработчик не выполняется в модальном случае.
- При использовании
Err
Я могу сказать, что если мы столкнемся с исключением в обработчике VBA, мы получим диалоговое окно ошибки VBA. Однажды очистив это, C++Invoke
вызов имеет 0x80020009 ("Возникла исключительная ситуация") в качестве кода возврата, а pExcepInfo заполнен общими значениями ошибок (VBA проглотил фактические данные) - Событие не запускается при втором отображении модального диалога, сразу после первого диалога или во время второго вызова надстройки C#.
Я попытаюсь покопаться в наших циклах сообщений в качестве следующего шага.
1 ответ
В этом вопросе слишком мало неопровержимых фактов, на которые можно найти ответ. Это может быть что-то очень простое, неприятная проблема с повреждением памяти или неясная зависимость внутри интерпретатора VBA от состояния потока. Грубая диагностика заключается в том, что обработчик событий VBA просто не запустился. В целом, это не редкость, декларативный стиль, используемый в Basic для объявления обработчиков событий, оставляет очень мало хороших способов диагностировать проблемы с подпиской. Многие программисты VBA потеряли пучки волос, пытаясь решить проблему "почему обработчик событий не запущен", как эта.
Сначала соберите некоторые факты и добавьте их к своему вопросу:
- Сначала убедитесь, что ваш код C++ действительно может видеть, что вообще нет обработчика событий. Используйте хорошую версию, переименуйте обработчик событий. Ожидается, что вы этого не сделаете, возникновение события, на которое подписчик не подписан, не является ошибкой.
- Убедитесь, что обработчик событий действительно выполнен в неверной версии. Пусть он делает что-то еще, кроме назначения аргумента BSTR, который вы можете легко увидеть как файл на диске.
- Убедитесь, что вы можете правильно диагностировать исключение в обработчике событий. Назначьте объект Err и убедитесь, что ваш код C++ генерирует правильную диагностику. Обратите внимание, что ваш вызов IDispatch::Invoke() передает значение NULL для pExcepInfo, что не является хорошим способом создания диагностики.
- Проверьте, выполняется ли событие во второй раз, когда вы отображаете окно, если это происходит, то у вас есть проблема порядка выполнения.
Сосредоточимся немного на ShowDialog(). Этот метод имеет много побочных эффектов. Первое, что больше не работает - это цикл сообщений в вашем коде C++. Теперь цикл сообщений.NET отправляет сообщения. Посмотрите на свои побочные эффекты, выполняя больше работы, чем просто GetMessage/DispatchMessage(). Эта работа больше не выполняется. Также ищите в вашей кодовой базе функцию PostThreadMessage(), эти сообщения падают на пол, когда код.NET качает.
И имейте в виду, что ваш собственный код C++ теряет контроль, когда код C# вызывает ShowDialog(). Он не восстанавливает контроль, пока вы не закроете окно. Это может вызвать простую проблему порядка выполнения, ваш код C++ не должен делать ничего важного после того, как он делает все, чтобы запустить код C#.