Я надеюсь, что каждый извинит за длину и повествовательность этого вопроса. Я решил описать ситуацию более подробно в своем блоге. Позже я увидел приглашение Джоэла на этот сайт и решил вставить его сюда, чтобы узнать, есть ли у кого-нибудь понимание ситуации.
Я написал и теперь поддерживаю приложение, состоящее из толстого клиента Visual Basic, говорящего от компонентов DCOM до COM + среднего уровня, написанных на C ++ с использованием ATL. Он работает во всех восьми наших офисах. В каждом офисе размещается внутренний сервер, содержащий приложение COM + (состоящее из 18 отдельных компонентов) и SQLServer. SQLServer обычно находится на том же внутреннем сервере, но не обязательно.
Недавно мы перенесли внутренний сервер в нашем крупнейшем офисе - Нью-Йорке - из кластера MSC на новую виртуальную машину, размещенную на технологии ESW от 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.
Идентификаторы интерфейса предоставили подсказку, необходимую для разгадки тайны. «Ожидаемый» идентификатор интерфейса идентифицирует интерфейс Recordset MDAC - в частности, версию 2.1 этого интерфейса. «Возвращенный» интерфейс соответствует более поздней версии Recordset (версия 2.5, которая отличается от версии 2.1 включением одной дополнительной записи в конце vtable - метода Save).
Действительно, интерфейсы моего компонента предоставляют множество методов, которые передают Recordset в качестве выходного параметра. Так они вдруг вернули более позднюю версию Recordset - с другим идентификатором интерфейса? Это, конечно, казалось, имело место. И тогда я подумал, почему это должно иметь значение. Vtable выглядит так же для клиентов старого интерфейса. Действительно, я подозреваю, что если бы мы говорили о внутрипроцессном COM, а не о DCOM, это явно безвредное несоответствие импеданса было бы незаметно проигнорировано и не вызвало бы никаких проблем.
Конечно, когда в игру вступают границы между процессами и машинами, между клиентом и сервером существует прокси и заглушка. В этом случае я использовал маршалинг библиотеки типов со свободным многопоточным маршаллером. Итак, нужно было решить две загадки:
Почему я возвращал другой интерфейс в параметрах вывода из методов на моем новом сервере?
Почему это коснулось только клиентов Vista?
Поскольку мое серверное программное обеспечение было размещено на серверах в каждом из моих восьми офисов, я решил попробовать нацелить свой клиент Vista на все из них по порядку, чтобы увидеть, у кого были проблемы с Vista, а какие нет. Осветительный тест. Некоторые из старых серверов все еще работали с Vista, но более новые - нет. Хотя некоторые из старых серверов все еще работали под управлением Windows 2000, в то время как новые были в 2003 году, проблема, похоже, не возникала.
После сравнения дат компонентных библиотек 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 набора записей в выходных параметрах моего метода. Очевидно, проблема была в моей перекомпиляции 2003 года и в том факте, что в то время символ _Recordset больше не соответствовал версии 2.1. Действительно _Recordset соответствует версии 2.5 с его отличным идентификатором интерфейса. Решением для меня было изменить все ссылки с _Recordset на Recordset21 в моем коде C ++. Я перестроил компоненты и развернул их на новом сервере. Вуаля - клиенты снова казались счастливыми.
В заключение, у меня остались два ноющих вопроса.
Почему инфраструктура прокси / заглушки работает с клиентами Vista по-разному? Похоже, что Vista делает более строгие проверки идентификаторов интерфейса, возвращающихся из параметров метода, чем XP.
Как я должен был по-другому это кодировать в 1999 году, чтобы этого не случилось? Предполагается, что интерфейсы являются неизменяемыми, и когда я перекомпилировал в более новую версию MDAC, я случайно изменил свой интерфейс, потому что методы теперь возвращали другой интерфейс Recordset в качестве выходного параметра. Насколько я знаю, в библиотеке типов в то время не было символа для конкретной версии, то есть более поздние версии библиотек типов MDAC определяют Recordset21, но этот символ не был доступен в библиотеке типов 2.1.