Использование директивы #import в Visual C++
- Как осуществить на VC создание документа и написать туда пару слов?
- В общем, нужно конвертить Word файлы в HTML программно. Помогите плиз.
- Возникла следующая проблема - необходимо загрузить документ Excel'а или
Word'а (вместе с программами - т.е. запускается Word и загружается в него
документ) и запустить в нем функцию или макрос на VBA.
- Имеется файл БД. Экселевский или эксесовский со след. полями: Необходимо
читать и писать (добавлять и изменять) в файл. Как это лучше сделать.
- Мысль хорошая, только я не знаю, как связываются переменные с окнами из
ресурса. Сейчас-то за меня в DoDataExchange все делают автоматически:
- А не подскажешь ли как работать с OLE?
Подобные вопросы часто можно встретить в конференциях Fidonet, посвящённых
программированию на Visual C++. Как правило, после некоторого обсуждения,
фидошная общественность приходит к мнению, что лучшее решение - использование
директивы #import.
В данной статье я попытаюсь объяснить то, как работает эта директива и
привести несколько примеров её использования. Надеюсь, после этого вы тоже
найдёте её полезной.
Директива #import введена в Visual C++, начиная с версии 5.0. Её
основное назначение облегчить подключение и использование интерфейсов COM,
описание которых реализовано в библиотеках типов.
Полное описание директивы приведено в MSDN в одной единственной статье,
которую можно найти по указателю, введя ключевое слово #import или по
содержанию:
Библиотека типов представляет собой файл или компонент внутри другого файла,
который содержит информацию о типе и свойствах COM объектов. Эти объекты
представляют собой, как правило, объекты OLE автоматизации. Программисты,
которые пишут на Visual Basic'е, используют такие объекты, зачастую сами того не
замечая. Это связано с тем, что поддержка OLE автоматизации вляется неотъемлемой
частью VB и при этом создаётся иллюзия того, что эти объекты также являются
частью VB.
Добиться такого же эффекта при работе на C++ невозможно (да и нужно ли?), но
можно упростить себе жизнь, используя классы представляющие обёртки (wrappers)
интерфейса IDispatch. Таких классов в библиотеках VC имеется несколько.
- Первый из них - COleDispatchDriver, входит в состав библиотеки MFC.
Для него имеется поддержка со стороны MFC ClassWizard'а, диалоговое окно
которого содержит кнопку Add Class и далее From a type library.
После выбора библиотеки типов и указания интерфейсов, которые мы хотим
использовать, будет сгенерирован набор классов, представляющих собой обёртки
выбранных нами интерфейсов. К сожалению, ClassWizard не генерирует константы,
перечисленные в библиотеке типов, игнорирует некоторые интерфейсы, добавляет к
именам свойств префиксы Put и Get и не отслеживает ссылок на другие библиотеки
типов.
- Второй - CComDispatchDriver является частью библиотеки ATL. Я не знаю
средств в VC, которые могли бы облегчить работу с этим классом, но у него есть
одна особенность - с его помощью можно вызывать методы и свойства объекта не
только по ID, но и по их именам, то есть использовать позднее связывание в
полном объёме.
- Третий набор классов - это результат работы директивы #import.
Последний способ доступа к объектам OLE Automation является наиболее
предпочтительным, так как предоставляет достаточно полный и довольно удобный
набор классов.
Рассмотрим пример.
Создадим IDL-файл, описывающий библиотеку типов. Наш
пример будет содержать описание одного перечисляемого типа SamplType и
описание одного объекта ISamplObject, который в свою очередь будет
содержать одно свойство Prop и один метод Method.
Sampl.idl:
После подключения соответствующей библиотеки типов с помощью директивы
#import будут созданы два файла, которые генерируются в выходном каталоге
проекта. Это файл sampl.tlh, содержащий описание классов, и файл
sampl.tli, который содержит реализацию членнов классов. Эти файлы будут
включены в проект автоматически. Ниже приведено содержимое этих файлов.
Sampl.tlh:
Sampl.tli:
Первое на что следует обратить внимание - это на строчку файла sampl.tlh:
Это означает, что компилятор помещает описание классов в отдельное
пространство имён, соответствующее имени библиотеки типов. Это является
необходимым при использовании нескольких библиотек типов с одинаковыми именами
классов, такими, например, как IDocument. При желании, имя пространства
имён можно изменить или запретить его генерацию совсем:
Теперь рассмотрим объявление метода Method:
Здесь мы видим использование компилятором классов поддержки COM. К таким
классам относятся следующие.
- _com_error. Этот класс используется для обработки исключительных
ситуаций, генерируемых библиотекой типов или каким либо другим классом поддержки
(например, класс _variant_t будет генерировать это исключение, если не
сможет произвести преобразование типов).
- _com_ptr_t. Этот класс определяет гибкий указатель для использования
с интерфейсами COM и применяется при создании и уничтожении объектов.
- _variant_t. Инкапсулирует тип данных VARIANT и может значительно
упростить код приложения, поскольку работа с данными VARIANT напрямую вляется
несколько трудоёмкой.
- _bstr_t. Инкапсулирует тип данных BSTR. Этот класс обеспечивает встроенную обработку процедур распределения и освобождения ресурсов, а также других операций.
Нам осталось уточнить природу класса ISamplObjectPtr. Мы уже говорили
о классе _com_ptr_t. Он используется для реализации smart-указателей на
интерфейсы COM. Мы будем часто использовать этот класс, но не будем делать этого
напрямую. Директива #import самостоятельно генерирует определение
smart-указателей. В нашем примере это сделано следующим образом.
Это объявление эквивалентно следующему:
Использование smart-указателей позволяет не думать о счётчиках ссылок на
объекты COM, т.к. методы AddRef и Release интерфейса
IUnknown вызываютс автоматически в перегруженных операторах класса
_com_ptr_t.
Помимо прочих этот класс имеет следующий перегруженный
оператор.
где Interface - тип интерфейса, в нашем случае - это ISamplObject. Таким образом мы сможем обращаться к свойствам и методам нашего COM объекта. Вот как будет выглядеть пример использования директивы #import для нашего примера (красным цветом выделены места использования перегруженного оператора).
Как видно из примера создавать объекты COM с использованием классов,
сгенерированных директивой #import, достаточно просто. Во-первых,
необходимо объявить smart-указатель на тип создаваемого объекта. После этого для
создания экземпляра нужно вызвать метод CreateInstance класса
_com_ptr_t, как показано в следующих примерах:
Можно упростить этот процесс, передавая идентификатор класса в конструктор
указателя:
Прежде чем перейти к примерам, нам необходимо рассмотреть обработку
исключительных ситуаций. Как говорилось ранее, директива #import
использует для генерации исключительных ситуаций класс _com_error. Этот
класс инкапсулирует генерируемые значения HRESULT, а также поддерживает
работу с интерфейсом IErrorInfo для получения более подробной информации
об ошибке. Внесём соответствующие изменения в наш пример:
При изучении файла sampl.tli хорошо видно как директива #import
генерирует исключения. Это происходит всегда при выполнении следующего условия:
Этот способ, безусловно, является универсальным, но могут возникнуть
некоторые неудобства. Например, метод MoveNext объекта Recordset ADO
возвращает код, который не является ошибкой, а лишь индицирует о достижении
конца набора записей. Тем не менее, мы получим исключение. В подобных случаях
придётся использовать либо вложенные операторы try {} catch, либо
корректировать wrapper, внося обработку исключений непосредственно в тело
сгенерированных процедур. В последнем случае, правда, придется подключать файлы
*.tlh уже обычным способом, через #include. Но делать это никто не
запрещает.
Наконец, настало время рассмотреть несколько практических примеров. Я приведу
четыре примера работы с MS Word, MS Excel, ADO DB и
ActiveX Control. Первые три примера будут обычными консольными
программами, в последнем примере я покажу, как можно заменить класс
COleDispatchDriver сгенерированный MFC Class Wizard'ом на классы
полученные директивой #import.
Для первых двух примеров нам понадобиться файл следующего содержания:
Этот файл содержит подключение библиотек типов MS Word, MS
Excel и MS Access. По умолчанию подключаются библиотеки для MS
Office 2000, если на вашем компьютере установлен MS Office 97, то
следует закомментировать строчку
Если MS Office установлен в каталог отличный от "C:\Program
Files\Microsoft Office\Office\", то пути к библиотекам также следует
подкорректировать. Обратите внимание на атрибут rename, его необходимо
использовать, когда возникают конфликты имён свойств и методов библиотеки типов
с препроцессором. Например, функция ExitWindows объявлена в файле
winuser.h как макрос:
В результате, там, где препроцессор встретит имя ExitWindows, он будет
пытаться подставлять определение макроса. Этого можно избежать при использовании
атрибута rename, заменив такое имя на любое другое.
MS Word
MS Excel
ADO DB
AciveX Control
Для этого примера нам понадобится любое оконное приложение. ActiveX
Control'ы вставляются в диалог обычно через Components and Controls
Gallery: Меню-Project-Add_To_Project-Components_and_Controls-Registered_ActiveX_Controls.
Нам в качестве примера вполне подойдёт Microsoft FlexGrid Control.
Нажмите кнопку Insert для добавления его в проект, в появившемся окне
Confirm Classes оставьте галочку только возле элемента CMSFlexGrid
и смело жмите OK. В результате будут сформированы два файла msflexgrid.h
и msflexgrid.cpp, большую часть содержимого которых нам придётся удалить.
После всех изменений эти файлы будут иметь следующий вид:
msflexgrid.h
msflexgrid.cpp
Теперь вставим элемент в любой диалог, например CAboutDlg. В диалог
добавим переменную связанную с классом CMSFlexGrid и метод
OnInitDialog, текст которого приведён ниже. При вызове диалога в наш
FlexGrid будут добавлены два элемента:
В заключении, позволю себе высказать ещё несколько замечаний.
- Всегда внимательно изучайте файлы *.tlh. Отчасти они могут заменить
документацию, а если её нет, то это единственный источник информации (кроме,
конечно, OLE/COM Object Viewer).
- Избегайте повторяющихся сложных конструкций. Например, можно написать так:
book->Worksheets->Item[1L]->Range["B2"]->FormulaR1C1 = "Строка 1"; book->Worksheets->Item[1L]->Range["C2"]->FormulaR1C1 = 12345L;
Но в данном случае вы получите неоправданное замедление из-за лишнего межзадачного взаимодействия, а в случае DCOM - сетевого взаимодействия. Лучше написать так:
_WorksheetPtr sheet = book->Worksheets->Item[1L]; sheet->Range["B2"]->FormulaR1C1 = "Строка 1"; sheet->Range["C2"]->FormulaR1C1 = 12345;
- При работе с MS Office максимально используйте возможности VBA
для подготовки и тестирования вашего кода. Приведённые примеры я сочинил за пару
минут, просто включив запись макроса, после чего скопировал полученный код в
свою программу, слегка оптимизировал его и адаптировал для C++. Например, я
понятия не имел, что объект Range имеет свойство FormulaR1C1, тем
не менее, я получил то, что хотел.
- Будьте внимательны с версиями библиотек типов. К примеру, в MS Word
2000 появилась новая версия метода Run. Старая тоже осталась, но она
имеет теперь название RunOld. Если вы используете MS Word 2000 и
вызываете метод Run, то забудьте о совместимости с MS Word 97,
метода с таким ID в MS Word 97 просто нет. Используйте вызов
RunOld и проблем не будет, хотя если очень хочется можно всегда проверить
номер версии MS Word.
- Бывают глюки :o(. Сразу замечу, что это не связано с самой директивой
#import. Например, при использовании класса COleDispatchDriver с
MSADODC.OCX у меня всё прекрасно работало, после того как я стал
использовать директиву #import, свойство ConnectionString
отказалось возвращать значение. Дело в том, что директива #import
генерирует обёртку, использу dual-интерфейс объекта, а класс
COleDispatchDriver вызывает ConnectionString через
IDispatch::Invoke. Ошибка, видимо, в реализации самого
MSADODC.OCX. После изменения кода вызова свойства всё заработало:
inline _bstr_t IAdodc::GetConnectionString () { BSTR _result; HRESULT _hr = <B>_com_dispatch_propget(this,0x01,VT_BSTR,&_result)</B>; // HRESULT _hr = <B>get_ConnectionString(&_result)</B>; if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this)); return _bstr_t(_result, false); }
- В результате раскрутки библиотек типов MS Office, компилятор нагенерирует вам в выходной каталог проекта около 12! Mb исходников. Всё это он потом, естественно, будет компилировать. Если вы не являетесь счастливым обладателем PIII, то наверняка заметите некоторые тормоза. В таких случаях я стараюсь выносить в отдельный файл всю работу, связанную с подобными библиотеками типов. Кроме того, компилятор может генерировать обёртки классов каждый раз после внесения изменений в файл, в который включена директива #import. Представьте, что будет, если после каждого нажатия клавиши будут заново генерироваться все 12 Mb? Лучше вынести объявление директивы #import в отдельный файл и подключать его через #include.
Удачи в бою.
Литература:
- Visual C++ 5. Руководство разработчика. Дэвид Беннет и др. Диалектика. 1998
Комментарии |
отсутствуют |
Добавление комментария |