Проблемы вызова интерфейса 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#.

Другие вопросы по тегам