Я надеюсь, что все простят за длину и повествовательную моду этого вопроса. Решил более подробно описать ситуацию в своем блоге. Позже я увидел приглашение Джоэла на этот сайт и подумал, что вставлю его сюда, чтобы посмотреть, есть ли у кого-нибудь представление о ситуации.
Я написал и теперь поддерживаю приложение, которое состоит из толстого клиента Visual Basic, передающего DCOM компонентам COM + среднего уровня, написанным на C ++ с использованием ATL. Он работает во всех восьми наших офисах. В каждом офисе размещается внутренний сервер, содержащий приложение COM + (состоящее из 18 отдельных компонентов) и SQLServer. SQLServer обычно находится на том же внутреннем сервере, но не обязательно.
Недавно мы перенесли внутренний сервер в нашем крупнейшем офисе - Нью-Йорке - из кластера MSC на новую виртуальную машину, размещенную на технологии ESX от VMWare. Поскольку местоположение приложения COM + было перемещено со старого сервера на новый с другим именем, мне пришлось перенаправить всех клиентов, чтобы они активировали приложение COM + на новом сервере. Процедура была устаревшей, поскольку я проделал, по сути, то же самое для нескольких моих небольших офисов, в которых была проведена аналогичная модернизация инфраструктуры.
Все казалось обычным делом, и в понедельник утром весь офис - около 1000 рабочих станций Windows XP - без сбоев работал на новом сервере. Но затем поступил звонок из моей мобильной группы - адвокат, работавший из дома с VPN-соединением, получал странную ошибку после перенаправления на новый сервер:
Error on FillTreeView2 - The stub received bad data.
Хм? Я никогда раньше не видел этого сообщения об ошибке. Это был новый сервер? Но все рабочие станции в офисе работали нормально. Я сказал мобильной группе переключить поверенного обратно на старый сервер (который все еще работал), и ошибка исчезла. Так в чем же разница? Оказывается, этот поверенный запускал Vista дома.
Мы не запускаем Vista ни в одном из наших офисов, но у нас есть несколько юристов, которые работают с Vista дома (конечно, некоторые в моем офисе в Нью-Йорке). Я тоже, и никогда не видел этой проблемы. Чтобы подтвердить, что возникла проблема, я включил свой ноутбук с Vista, указал на новый сервер и получил ту же ошибку. Я направил его обратно на старый сервер, и он работал нормально. Ясно, что была какая-то проблема с Vista и компонентами на новом сервере - проблема, которая, похоже, не затрагивала клиентов XP. Что бы это могло быть?
Следующая остановка - журнал ошибок приложения на моем ноутбуке. Это дало дополнительную информацию об ошибке:
Source: Microsoft-Windows-RPC-Events Date: 9/2/2008 11:56:07 AM Event ID: 10 Level: Error Computer: DevLaptop Description: Application has failed to complete a COM call because an incorrect interface ID was passed as a parameter. The expected Interface ID was 00000555-0000-0010-8000-00aa006d2ea4, The Interface ID returned was 00000556-0000-0010-8000-00aa006d2ea4. User Action - Contact the application vendor for updated version of the application.
Идентификаторы интерфейса дали мне ключ к разгадке тайны. «Ожидаемый» идентификатор интерфейса определяет интерфейс набора записей MDAC, в частности версию 2.1 этого интерфейса. «Возвращенный» интерфейс соответствует более поздней версии Recordset (версия 2.5, которая отличается от версии 2.1 включением одной дополнительной записи в конце vtable - метода Save).
Действительно, интерфейсы моего компонента предоставляют множество методов, которые передают Recordset в качестве выходного параметра. Неужели они внезапно вернули более позднюю версию Recordset - с другим идентификатором интерфейса? Оказалось, что так оно и есть. А потом я подумал, какое это имеет значение. Таблица vtable выглядит одинаково для клиентов старого интерфейса. В самом деле, я подозреваю, что если бы мы говорили о внутрипроцессном COM, а не о DCOM, это очевидно безобидное несоответствие импеданса было бы тихо проигнорировано и не вызвало бы никаких проблем.
Конечно, когда в игру вступают границы процессов и машин, между клиентом и сервером есть прокси и заглушка. В этом случае я использовал маршалинг библиотеки типов с помощью бесплатного потокового маршаллера. Итак, предстояло разгадать две загадки:
Почему я возвращал другой интерфейс в выходных параметрах из методов на моем новом сервере?
Почему это коснулось только клиентов Vista?
Поскольку мое серверное программное обеспечение было размещено на серверах в каждом из восьми моих офисов, я решил попробовать направить свой клиент Vista на все из них по порядку, чтобы увидеть, у кого были проблемы с Vista, а у кого нет. Световой тест. Некоторые старые серверы все еще работали с Vista, а новые - нет. Хотя на некоторых старых серверах все еще использовалась Windows 2000, а на новых - 2003, это не похоже на проблему.
После сравнения дат компонентных DLL выяснилось, что всякий раз, когда клиент указывал на серверы с компонентными DLL, датированными до 2003 года, Vista работала нормально. Но те, у которых были библиотеки DLL с датой после 2003 года, были проблематичными. Вы не поверите, но за многие годы в коде серверных компонентов не было (или, по крайней мере, не было существенных) изменений. Очевидно, разные даты были просто из-за перекомпиляции моих компонентов на моей машине (ах) разработки. И оказалось, что одна из таких перекомпиляций произошла в 2003 году.
Лампочка загорелась. При передаче наборов записей обратно с сервера на клиент мои компоненты ATL C ++ ссылаются на интерфейс как на _Recordset. Этот символ взят из библиотеки типов, встроенной в msado15.dll. Это строка, которая у меня была в коде C ++:
#import "c:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename ( "EOF", "adoEOF" )
Не обманывайтесь числом 15 в msdad15.dll. Судя по всему, эта DLL не меняла название в длинной серии версий MDAC.
Когда я скомпилировал приложение, версия MDAC была 2.1. Итак, _Recordset скомпилирован с идентификатором интерфейса 2.1, и это интерфейс, возвращаемый серверами, на которых запущены эти компоненты.
Все клиенты используют прокси-сервер приложения COM +, который был создан (я полагаю) еще в 1999 году. Библиотека типов, определяющая мои интерфейсы, включает строку:
importlib("msado21.tlb");
что объясняет, почему они ожидают версии 2.1 Recordset в выходных параметрах моего метода. Очевидно, проблема была в моей перекомпиляции 2003 года и в том факте, что в то время символ _Recordset больше не соответствовал версии 2.1. Действительно, _Recordset соответствовал версии 2.5 с отдельным идентификатором интерфейса. Решением для меня было изменить все ссылки с _Recordset на Recordset21 в моем коде C ++. Я восстановил компоненты и развернул их на новом сервере. Вуаля - клиенты снова казались счастливыми.
В заключение хочу сказать, что у меня остаются два мучительных вопроса.
Почему кажется, что инфраструктура прокси / заглушки ведет себя иначе с клиентами Vista? Похоже, что Vista делает более строгие проверки идентификаторов интерфейсов, возвращаемых из параметров метода, чем XP.
Как я должен был закодировать это по-другому в 1999 году, чтобы этого не произошло? Интерфейсы должны быть неизменными, и когда я перекомпилировал под более новую версию MDAC, я случайно изменил свой интерфейс, потому что теперь методы возвращали другой интерфейс Recordset в качестве выходного параметра. Насколько мне известно, тогда в библиотеке типов не было символа, зависящего от версии, то есть более поздние версии библиотек типов MDAC определяют Recordset21, но этот символ не был доступен в библиотеке типов 2.1.