Как осуществить на VC создание документа и написать туда пару слов?
В общем, нужно конвертить Word файлы в HTML программно. Помогите плиз.
Возникла следующая проблема - необходимо загрузить документ Excel'а или
Word'а (вместе с программами - т.е. запускается Word и загружается в него
документ) и запустить в нем функцию или макрос на VBA.
Имеется файл БД. Экселевский или эксесовский со след. полями: Необходимо
читать и писать (добавлять и изменять) в файл. Как это лучше сделать.
Мысль хорошая, только я не знаю, как связываются переменные с окнами из
ресурса. Сейчас-то за меня в DoDataExchange все делают автоматически:
А не подскажешь ли как работать с OLE?
Подобные вопросы часто можно встретить в конференциях Fidonet, посвящённых
программированию на Visual C++. Как правило, после некоторого обсуждения,
фидошная общественность приходит к мнению, что лучшее решение - использование
директивы #import.
В данной статье я попытаюсь объяснить то, как работает эта директива и
привести несколько примеров её использования. Надеюсь, после этого вы тоже
найдёте её полезной.
Директива #import введена в Visual C++, начиная с версии 5.0. Её
основное назначение облегчить подключение и использование интерфейсов COM,
описание которых реализовано в библиотеках типов.
Полное описание директивы приведено в MSDN в одной единственной статье,
которую можно найти по указателю, введя ключевое слово #import или по
содержанию:
CODE NOW!
MSDN Library
Visual C++ Documentation
Using Visual C++
Visual C++ Programmer's Guide
Preprocessor Reference
The Preprocessor
Preprocessor Directives
The #import Directive
Библиотека типов представляет собой файл или компонент внутри другого файла,
который содержит информацию о типе и свойствах 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:
CODE NOW!
// Sampl.idl : IDL source for Sampl.dll
// This file will be processed by the MIDL tool to
// produce the type library (Sampl.tlb) and marshalling code.
После подключения соответствующей библиотеки типов с помощью директивы #import будут созданы два файла, которые генерируются в выходном каталоге
проекта. Это файл sampl.tlh, содержащий описание классов, и файл sampl.tli, который содержит реализацию членнов классов. Эти файлы будут
включены в проект автоматически. Ниже приведено содержимое этих файлов.
Sampl.tlh:
CODE NOW!
// Created by Microsoft (R) C/C++ Compiler Version 12.00.8472.0 (53af584f).
//
// sampl.tlh
//
// C++ source equivalent of Win32 type library Debug\sampl.dll
// compiler-generated file created 03/14/00 at 20:43:40 - DO NOT EDIT!
// Created by Microsoft (R) C/C++ Compiler Version 12.00.8472.0 (53af584f).
//
// sampl.tli
//
// Wrapper implementations for Win32 type library Debug\sampl.dll
// compiler-generated file created 03/14/00 at 20:43:40 - DO NOT EDIT!
Первое на что следует обратить внимание - это на строчку файла sampl.tlh:
CODE NOW!
namespace SAMPLLib {
Это означает, что компилятор помещает описание классов в отдельное
пространство имён, соответствующее имени библиотеки типов. Это является
необходимым при использовании нескольких библиотек типов с одинаковыми именами
классов, такими, например, как IDocument. При желании, имя пространства
имён можно изменить или запретить его генерацию совсем:
Здесь мы видим использование компилятором классов поддержки 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. Помимо прочих этот класс имеет следующий перегруженный
оператор.
CODE NOW!
Interface* operator->() const throw(_com_error);
где Interface - тип интерфейса, в нашем случае - это ISamplObject.
Таким образом мы сможем обращаться к свойствам и методам нашего COM объекта. Вот
как будет выглядеть пример использования директивы #import для нашего
примера (красным цветом выделены места использования перегруженного оператора).
Как видно из примера создавать объекты COM с использованием классов,
сгенерированных директивой #import, достаточно просто. Во-первых,
необходимо объявить smart-указатель на тип создаваемого объекта. После этого для
создания экземпляра нужно вызвать метод CreateInstance класса _com_ptr_t, как показано в следующих примерах:
CODE NOW!
SAMPLLib::ISamplObjectPtr obj;
obj.CreateInstance(L"SAMPLLib.SamplObject"); или
obj.CreateInstance(__uuidof(SamplObject));
Можно упростить этот процесс, передавая идентификатор класса в конструктор
указателя:
CODE NOW!
SAMPLLib::ISamplObjectPtr obj(L"SAMPLLib.SamplObject"); или
SAMPLLib::ISamplObjectPtr obj(__uuidof(SamplObject));
Прежде чем перейти к примерам, нам необходимо рассмотреть обработку
исключительных ситуаций. Как говорилось ранее, директива #import
использует для генерации исключительных ситуаций класс _com_error. Этот
класс инкапсулирует генерируемые значения HRESULT, а также поддерживает
работу с интерфейсом IErrorInfo для получения более подробной информации
об ошибке. Внесём соответствующие изменения в наш пример:
При изучении файла sampl.tli хорошо видно как директива #import
генерирует исключения. Это происходит всегда при выполнении следующего условия:
CODE NOW!
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
Этот способ, безусловно, является универсальным, но могут возникнуть
некоторые неудобства. Например, метод 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, то
следует закомментировать строчку
CODE NOW!
#define Uses_MSO2000
Если MS Office установлен в каталог отличный от "C:\Program
Files\Microsoft Office\Office\", то пути к библиотекам также следует
подкорректировать. Обратите внимание на атрибут rename, его необходимо
использовать, когда возникают конфликты имён свойств и методов библиотеки типов
с препроцессором. Например, функция ExitWindows объявлена в файле winuser.h как макрос:
В результате, там, где препроцессор встретит имя ExitWindows, он будет
пытаться подставлять определение макроса. Этого можно избежать при использовании
атрибута rename, заменив такое имя на любое другое.
MS Word
CODE NOW!
// console.cpp : Defines the entry point for the console application.
// создаём новый документ
_DocumentPtr wdoc1 = word->Documents->Add();
// пишем пару слов
RangePtr range = wdoc1->Content;
range->LanguageID = wdRussian;
range->InsertAfter("Пара слов");
// сохраняем как HTML
wdoc1->SaveAs(&_variant_t("C:\\MyDoc\\test.htm"), &_variant_t(long(wdFormatHTML)));
// иногда придется прибегать к явному преобразованию типов,
// т.к. оператор преобразования char* в VARIANT* не определён
// создаём новую книгу
_WorkbookPtr book = excel->Workbooks->Add();
// получаем первый лист (в VBA нумерация с единицы)
_WorksheetPtr sheet = book->Worksheets->Item[1L];
// Аналогичная конструкция на VBA выглядит так:
// book.Worksheets[1]
// В библиотеке типов Item объявляется как метод или
// свойство по умолчанию (id[0]), поэтому в VB его
// можно опускать. На C++ такое, естественно, не пройдёт.
// типа делаем красиво :o)
sheet->Range["A4:D4"]->->ColorIndex = 27L;
sheet->Range["A4:D4"]->Interior->ColorIndex = 5L;
// Постфикс L говорит, что константа является числом типа long.
// Вы всегда должны приводить числа к типу long или short при
// преобразованию их к _variant_t, т.к. преобразование типа int
// к _variant_t не реализовано. Это вызвано не желанием
// разработчиков компилятора усложнить нам жизнь, а спецификой
// самого типа int.
CharToOem(buf,buf); // только для косольных приложений
printf(buf);
}
::CoUninitialize();
}
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, большую часть содержимого которых нам придётся удалить.
После всех изменений эти файлы будут иметь следующий вид:
if (SUCCEEDED(GetControlUnknown()->QueryInterface(I.GetIID(),
(void**)&pInterface))) {
ASSERT(pInterface != NULL);
I.Attach(pInterface);
}
}
Теперь вставим элемент в любой диалог, например CAboutDlg. В диалог
добавим переменную связанную с классом CMSFlexGrid и метод OnInitDialog, текст которого приведён ниже. При вызове диалога в наш
FlexGrid будут добавлены два элемента:
В заключении, позволю себе высказать ещё несколько замечаний.
Всегда внимательно изучайте файлы *.tlh. Отчасти они могут заменить
документацию, а если её нет, то это единственный источник информации (кроме,
конечно, OLE/COM Object Viewer).
Избегайте повторяющихся сложных конструкций. Например, можно написать так:
Но
в данном случае вы получите неоправданное замедление из-за лишнего межзадачного
взаимодействия, а в случае DCOM - сетевого взаимодействия. Лучше написать так:
При работе с 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. После изменения кода вызова свойства всё заработало:
В результате раскрутки библиотек типов MS Office, компилятор
нагенерирует вам в выходной каталог проекта около 12! Mb исходников. Всё
это он потом, естественно, будет компилировать. Если вы не являетесь счастливым
обладателем PIII, то наверняка заметите некоторые тормоза. В таких
случаях я стараюсь выносить в отдельный файл всю работу, связанную с подобными
библиотеками типов. Кроме того, компилятор может генерировать обёртки классов
каждый раз после внесения изменений в файл, в который включена директива #import. Представьте, что будет, если после каждого нажатия клавиши будут
заново генерироваться все 12 Mb? Лучше вынести объявление директивы #import в отдельный файл и подключать его через #include.
Удачи в бою.
Литература:
Visual C++ 5. Руководство разработчика. Дэвид Беннет и др. Диалектика. 1998
Комментарии
отсутствуют
Добавление комментария
:: Последние обновления ::
04.09.2011 Долгожданный релиз VB Decompiler. Масса улучшений декомпиляции Native Code. Значительно расширенна и обновлена справочная система на русском и английском языках.
20.12.2010 DotFix Software поздравляет наших клиентов и посетителей сайта с наступающим Новым Годом и рождеством! Желаем приятно провести праздники и успехов в новом году!
28.11.2010 Выпущена новая версия защиты DotFix NiceProtect. Основные изменения коснулись обфускатора Delphi программ. Теперь имеется полная поддержка Tab и Page контролов на формах, что обеспечивает максимальную совместимость обфускации с Delphi XE программами.
21.10.2010 Обновлен декомпилятор Visual Basic программ до версии 8.1. Декомпиляция P-Code программ становится все более идеальной, также проделана большая работа по улучшению анализа Native Code и .NET приложений.
16.09.2009 Полностью обновлен движок сайта! Теперь все ссылки имеют читаемый понятный вид, разного рода глюки на страницах убраны. И теперь сайт полноценно работает на второй версии нашего движка.