Ошибка при работе с Word (Interop) в C# - "Заглушке переданы неправильные данные / Stub recieved bad data"

Редкая и экзотическая ошибка, способная при удачном стечении обстоятельств съесть значительную часть мозга среднестатистического программиста. У меня она проявилась на одной-единственной машине, ничем не отличавшейся от остальных (XP SP3, Office 2003), куда программу установили из чистого любопытства. Симптомы просты: вылет метода Execute интерфейса Find с крайне невнятной ошибкой "Заглушке переданы неправильные данные / Stub recieved bad data". Изюминка данной ошибки в том, что это глюк в интерфейсе Interop.Word, официально признанный Microsoft и распространяющийся на интерфейсы Find, Replacement, Dictionary, ReadabilityStatistics, ReadabilityStatistic и библиотеки Word версий 8.0, 8.1, 10.0 и 11.0.

Причина ошибки еще интересней. Word использует глобальные уникальные идентификаторы (Globally Unique Identifiers, GUIDs), которые ранее использовались Excel 95. Если библиотека Excel 95 была зарегистрирована в системе позднее библиотеки Word, то эти идентификаторы начинают указывать на библиотеку Excel. В результате COM может построить неправильную таблицу-посредника (proxy v-table) для клиентов вне процесса (out-of-process client ), использующих раннее связывание с Word. В итоге запросы к этой таблице вызывают либо ошибку либо падение, так как вместо Word передаются в Excel. Только не спрашивайте меня, каким образом библиотека Excel 95 могла зарегистрироваться в системе после библиотеки Word 2003. Подробности можно найти с следующих статьях BUG: NWFREADME: Word Interoperability Sample Generates An Exception Error Using the Word Find Object и Automation client receives an error message or crashes when the client calls the Find object in Word, я же перейду к самом интересному - работающему коду решения проблемы.

Справится с Microsoft-чудищем можно двумя путями - перерегистрацией библиотеки (не рекомендуется) и использованием позднего связывания (late binding) в нашем коде. Толкового описания позднего связывания в рунете я не нашел (разве что статью на msdn про Visual Basic), очень кратко и неточно его можно описать как поиск метода в уже созданном объекте во время выполнения программы по имени, вместо обращения к нему через заранее объявленный тип данных (класс, интерфейс). Так или иначе на практике это будет выглядеть вот так:

Берем стандартный поиск и замену строки через интерфейс Interop.Word.Find (примеры взяты частично из этой статьи, частично из новой версии класса, о которой напишу позже)

wordRange.Find.Execute(ref strToFindObj, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing, ref replaceStrObj, ref replaceTypeObj, ref wordMissing, ref wordMissing, ref wordMissing, ref wordMissing);

указываем

using System.Reflection;

и заменяем наш белый и пушистый код на хитрую загогулину:

    Word.Find wordFindObj = wordRange.Find;
    object[] wordFindParameters = new object[15] { strToFindObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, _missingObj, replaceStrObj, replaceTypeObj, _missingObj, _missingObj, _missingObj, _missingObj };

    wordFindObj.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, wordFindObj, wordFindParameters);

Есть еще одна тонкость, возвращаемый методом параметр мы получим в формате Object и если он используется в нашей программе (Execute возвращает bool, true в случае успеха поиска), необходимо выполнить явное приведение типа

rangeFound = (bool)wordFindObj.GetType().InvokeMember("Execute", BindingFlags.InvokeMethod, null, wordFindObj, wordFindParameters);

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *


*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>