Использование директивы #import в Visual C++
Игорь Ткачёв
it@hotmail.ru
- PS>
- Как осуществить на VC создание документа и
написать туда пару слов?
- NS>
- В общем, нужно конвертить Word файлы в HTML
программно. Помогите плиз.
- VV>
- Возникла следующая проблема - необходимо
загрузить документ Excel'а или Word'а (вместе с
программами - т.е. запускается Word и загружается в
него документ) и запустить в нем функцию или
макрос на VBA.
- MK>
- Имеется файл БД. Экселевский или эксесовский со
след. полями… Необходимо читать и писать
(добавлять и изменять) в файл. Как это лучше
сделать.
- SR>
- Мысль хорошая, только я не знаю, как связываются
переменные с окнами из ресурса. Сейчас-то за меня
в DoDataExchange все делают автоматически…
- YI>
- А не подскажешь ли как работать с OLE?
Подобные вопросы часто можно встретить в
конференциях Fidonet, посвящённых программированию
на Visual C++. Как правило, после некоторого
обсуждения, фидошная общественность приходит к
мнению, что лучшее решение - использование
директивы #import.
В данной статье я попытаюсь объяснить то, как
работает эта директива и привести несколько
примеров её использования. Надеюсь, после этого
вы тоже найдёте её полезной.
Директива #import введена в Visual C++, начиная с
версии 5.0. Её основное назначение облегчить
подключение и использование интерфейсов COM,
описание которых реализовано в библиотеках
типов.
Полное описание директивы приведено в MSDN в
одной единственной статье, которую можно найти
по указателю, введя ключевое слово #import или по
содержанию:
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:
// 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 "oaidl.idl";
import "ocidl.idl";
[
uuid(37A3AD11-F9CC-11D3-8D3C-0000E8D9FD76),
version(1.0),
helpstring("Sampl 1.0 Type Library")
]
library SAMPLLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");
typedef enum {
SamplType1 = 1,
SamplType2 = 2
} SamplType;
[
object,
uuid(37A3AD1D-F9CC-11D3-8D3C-0000E8D9FD76),
dual,
helpstring("ISamplObject Interface"),
pointer_default(unique)
]
interface ISamplObject : IDispatch
{
[propget, id(1)] HRESULT Prop([out, retval] SamplType *pVal);
[propput, id(1)] HRESULT Prop([in] SamplType newVal);
[id(2)] HRESULT Method([in] VARIANT Var,[in]
BSTR Str,[out, retval] ISamplObject** Obj);
};
[
uuid(37A3AD1E-F9CC-11D3-8D3C-0000E8D9FD76),
helpstring("SamplObject Class")
]
coclass SamplObject
{
[default] interface ISamplObject;
};
};
После подключения соответствующей библиотеки
типов с помощью директивы #import будут созданы
два файла, которые генерируются в выходном
каталоге проекта. Это файл sampl.tlh, содержащий
описание классов, и файл sampl.tli, который
содержит реализацию членнов классов. Эти файлы
будут включены в проект автоматически. Ниже
приведено содержимое этих файлов.
Sampl.tlh:
// 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!
#pragma once
#pragma pack(push, 8)
#include <comdef.h>
namespace SAMPLLib {
// Forward references and typedefs
struct __declspec(uuid("37a3ad1d-f9cc-11d3-8d3c-0000e8d9fd76"))
/* dual interface */ ISamplObject;
struct /* coclass */ SamplObject;
// Smart pointer typedef declarations
_COM_SMARTPTR_TYPEDEF(ISamplObject, __uuidof(ISamplObject));
// Type library items
enum SamplType
{
SamplType1 = 1,
SamplType2 = 2
};
struct __declspec(uuid("37a3ad1d-f9cc-11d3-8d3c-0000e8d9fd76"))
ISamplObject : IDispatch
{
// Property data
__declspec(property(get=GetProp,put=PutProp)) enum SamplType Prop;
// Wrapper methods for error-handling
enum SamplType GetProp ( );
void PutProp (enum SamplType pVal );
ISamplObjectPtr Method (const _variant_t & Var,_bstr_t Str );
// Raw methods provided by interface
virtual HRESULT __stdcall get_Prop (enum SamplType * pVal) = 0 ;
virtual HRESULT __stdcall put_Prop (enum SamplType pVal) = 0 ;
virtual HRESULT __stdcall raw_Method (VARIANT Var,BSTR Str,
struct ISamplObject** Obj) = 0 ;
};
struct __declspec(uuid("37a3ad1e-f9cc-11d3-8d3c-0000e8d9fd76")) SamplObject;
#include "debug\sampl.tli"
} // namespace SAMPLLib
#pragma pack(pop)
Sampl.tli:
// 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!
#pragma once
// interface ISamplObject wrapper method implementations
inline enum SamplType ISamplObject::GetProp ( ) {
enum SamplType _result;
HRESULT _hr = get_Prop(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _result;
}
inline void ISamplObject::PutProp ( enum SamplType pVal ) {
HRESULT _hr = put_Prop(pVal);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
}
inline ISamplObjectPtr ISamplObject::Method ( const _variant_t & Var, _bstr_t Str ) {
struct ISamplObject * _result;
HRESULT _hr = raw_Method(Var, Str, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return ISamplObjectPtr(_result, false);
}
Первое на что следует обратить внимание - это на
строчку файла sampl.tlh:
namespace SAMPLLib {
Это означает, что компилятор помещает описание
классов в отдельное пространство имён,
соответствующее имени библиотеки типов. Это
является необходимым при использовании
нескольких библиотек типов с одинаковыми
именами классов, такими, например, как IDocument.
При желании, имя пространства имён можно
изменить или запретить его генерацию совсем:
#import "sampl.dll" rename_namespace("NewNameSAMPLLib")
#import "sampl.dll" no_namespace
Теперь рассмотрим объявление метода Method:
ISamplObjectPtr Method (const _variant_t & Var,_bstr_t Str);
Здесь мы видим использование компилятором
классов поддержки 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 pointer typedef declarations
_COM_SMARTPTR_TYPEDEF(ISamplObject,__uuidof(ISamplObject));
Это объявление эквивалентно следующему:
typedef _com_ptr_t<ISamplObject,&__uuidof(ISamplObject)> ISamplObjectPtr
Использование smart-указателей позволяет не
думать о счётчиках ссылок на объекты COM, т.к.
методы AddRef и Release интерфейса IUnknown
вызываютс автоматически в перегруженных
операторах класса _com_ptr_t.
Помимо прочих этот класс имеет следующий
перегруженный оператор.
Interface* operator->() const throw(_com_error);
где Interface - тип интерфейса, в нашем случае -
это ISamplObject. Таким образом мы сможем
обращаться к свойствам и методам нашего COM
объекта. Вот как будет выглядеть пример
использования директивы #import для нашего
примера (красным цветом выделены места
использования перегруженного оператора).
#import "sampl.dll"
void SamplFunc ()
{
SAMPLLib::ISamplObjectPtr obj;
obj.CreateInstance(L"SAMPLLib.SamplObject");
SAMPLLib::ISamplObjectPtr obj2 = obj->Method(1l,L"12345");
obj->Prop = SAMPLLib::SamplType2;
obj2->Prop = obj->Prop;
}
Как видно из примера создавать объекты COM с
использованием классов, сгенерированных
директивой #import, достаточно просто. Во-первых,
необходимо объявить smart-указатель на тип
создаваемого объекта. После этого для создания
экземпляра нужно вызвать метод CreateInstance класса
_com_ptr_t, как показано в следующих примерах:
SAMPLLib::ISamplObjectPtr obj;
obj.CreateInstance(L"SAMPLLib.SamplObject");
или
obj.CreateInstance(__uuidof(SamplObject));
Можно упростить этот процесс, передавая
идентификатор класса в конструктор указателя:
SAMPLLib::ISamplObjectPtr obj(L"SAMPLLib.SamplObject");
или
SAMPLLib::ISamplObjectPtr obj(__uuidof(SamplObject));
Прежде чем перейти к примерам, нам необходимо
рассмотреть обработку исключительных ситуаций.
Как говорилось ранее, директива #import
использует для генерации исключительных
ситуаций класс _com_error. Этот класс
инкапсулирует генерируемые значения HRESULT, а
также поддерживает работу с интерфейсом IErrorInfo для
получения более подробной информации об ошибке.
Внесём соответствующие изменения в наш пример:
#import "sampl.dll"
void SamplFunc ()
{
try {
using namespace SAMPLLib;
ISamplObjectPtr obj(L"SAMPLLib.SamplObject");
ISamplObjectPtr obj2 = obj->Metod(1l,L"12345");
obj->Prop = SAMPLLib::SamplType2;
obj2->Prop = obj->Prop;
} catch (_com_error& er) {
printf("_com_error:\n"
"Error : %08lX\n"
"ErrorMessage: %s\n"
"Description : %s\n"
"Source : %s\n",
er.Error(),
(LPCTSTR)_bstr_t(er.ErrorMessage()),
(LPCTSTR)_bstr_t(er.Description()),
(LPCTSTR)_bstr_t(er.Source()));
}
}
При изучении файла sampl.tli хорошо видно как
директива #import генерирует исключения. Это
происходит всегда при выполнении следующего
условия:
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.
Для первых двух примеров нам понадобиться файл
следующего содержания:
// Office.h
#define Uses_MSO2000
#ifdef Uses_MSO2000
// for MS Office 2000
#import "C:\Program Files\Microsoft Office\Office\MSO9.DLL"
#import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBA6\VBE6EXT.OLB"
#import "C:\Program Files\Microsoft Office\Office\MSWORD9.OLB" \
rename("ExitWindows","_ExitWindows")
#import "C:\Program Files\Microsoft Office\Office\EXCEL9.OLB" \
rename("DialogBox","_DialogBox") \
rename("RGB","_RGB") \
exclude("IFont","IPicture")
#import "C:\Program Files\Common Files\Microsoft Shared\DAO\DAO360.DLL" \
rename("EOF","EndOfFile") rename("BOF","BegOfFile")
#import "C:\Program Files\Microsoft Office\Office\MSACC9.OLB"
#else
// for MS Office 97
#import "C:\Program Files\Microsoft Office\Office\MSO97.DLL"
#import "C:\Program Files\Common Files\Microsoft Shared\VBA\VBEEXT1.OLB"
#import "C:\Program Files\Microsoft Office\Office\MSWORD8.OLB" \
rename("ExitWindows","_ExitWindows")
#import "C:\Program Files\Microsoft Office\Office\EXCEL8.OLB" \
rename("DialogBox","_DialogBox") \
rename("RGB","_RGB") \
exclude("IFont","IPicture")
#import "C:\Program Files\Common Files\Microsoft Shared\DAO\DAO350.DLL" \
rename("EOF","EndOfFile")
rename("BOF","BegOfFile")
#import "C:\Program Files\Microsoft Office\Office\MSACC8.OLB"
#endif
Этот файл содержит подключение библиотек типов
MS Word, MS Excel и MS Access. По умолчанию
подключаются библиотеки для MS Office 2000, если на
вашем компьютере установлен MS Office 97, то
следует закомментировать строчку
#define Uses_MSO2000
Если MS Office установлен в каталог отличный от "C:\Program
Files\Microsoft Office\Office\", то пути к библиотекам
также следует подкорректировать. Обратите
внимание на атрибут rename, его необходимо
использовать, когда возникают конфликты имён
свойств и методов библиотеки типов с
препроцессором. Например, функция ExitWindows
объявлена в файле winuser.h как макрос:
#define ExitWindows(dwReserved,Code) ExitWindowsEx(EWX_LOGOFF,0xFFFFFFFF)
В результате, там, где препроцессор встретит
имя ExitWindows, он будет пытаться подставлять
определение макроса. Этого можно избежать при
использовании атрибута rename, заменив такое
имя на любое другое.
MS Word
// console.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <stdio.h>
#include "Office.h"
void main()
{
::CoInitialize(NULL);
try {
using namespace Word;
_ApplicationPtr word(L"Word.Application");
word->Visible = true;
word->Activate();
// создаём новый документ
_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* не определён
// открывает документ test.doc
_DocumentPtr wdoc2 = word->Documents->Open(&_variant_t("C:\\MyDoc\\test.doc"));
// вызываем макрос
word->Run("Macro1");
} catch (_com_error& er) {
char buf[1024];
sprintf(buf,"_com_error:\n"
"Error : %08lX\n"
"ErrorMessage: %s\n"
"Description : %s\n"
"Source : %s\n",
er.Error(),
(LPCTSTR)_bstr_t(er.ErrorMessage()),
(LPCTSTR)_bstr_t(er.Description()),
(LPCTSTR)_bstr_t(er.Source()));
CharToOem(buf,buf); // только для косольных приложений
printf(buf);
}
::CoUninitialize();
}
MS Excel
// console.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <stdio.h>
#include "Office.h"
void main()
{
::CoInitialize(NULL);
try {
using namespace Excel;
_ApplicationPtr excel("Excel.Application");
excel->Visible[0] = true;
// создаём новую книгу
_WorkbookPtr book = excel->Workbooks->Add();
// получаем первый лист (в VBA нумерация с единицы)
_WorksheetPtr sheet = book->Worksheets->Item[1L];
// Аналогичная конструкция на VBA выглядит так:
// book.Worksheets[1]
// В библиотеке типов Item объявляется как метод или
// свойство по умолчанию (id[0]), поэтому в VB его
// можно опускать. На C++ такое, естественно, не пройдёт.
// заполняем ячейки
sheet->Range["B2"]->FormulaR1C1 = "Строка 1";
sheet->Range["C2"]->FormulaR1C1 = 12345L;
sheet->Range["B3"]->FormulaR1C1 = "Строка 2";
sheet->Range["C3"]->FormulaR1C1 = 54321L;
// заполняем и активизируем итоговую строку
sheet->Range["B4"]->FormulaR1C1 = "Итого:";
sheet->Range["C4"]->FormulaR1C1 = "=SUM(R[-2]C:R[-1]C)";
sheet->Range["C4"]->Activate();
// типа делаем красиво :o)
sheet->Range["A4:D4"]->Font->ColorIndex = 27L;
sheet->Range["A4:D4"]->Interior->ColorIndex = 5L;
// Постфикс L говорит, что константа является числом типа long.
// Вы всегда должны приводить числа к типу long или short при
// преобразованию их к _variant_t, т.к. преобразование типа int
// к _variant_t не реализовано. Это вызвано не желанием
// разработчиков компилятора усложнить нам жизнь, а спецификой
// самого типа int.
} catch (_com_error& er) {
char buf[1024];
sprintf(buf,"_com_error:\n"
"Error : %08lX\n"
"ErrorMessage: %s\n"
"Description : %s\n"
"Source : %s\n",
er.Error(),
(LPCTSTR)_bstr_t(er.ErrorMessage()),
(LPCTSTR)_bstr_t(er.Description()),
(LPCTSTR)_bstr_t(er.Source()));
CharToOem(buf,buf); // только для косольных приложений
printf(buf);
}
::CoUninitialize();
}
ADO DB
// console.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <stdio.h>
#import "C:\Program Files\Common Files\System\ado\msado20.tlb" \
rename("EOF","ADOEOF") rename("BOF","ADOBOF")
// оператор rename необходим, т.к. EOF определён как макрос
// в файле stdio.h
using namespace ADODB;
void main()
{
::CoInitialize(NULL);
try {
// открываем соединение с БД
_ConnectionPtr con("ADODB.Connection");
con->Open(L"Provider=Microsoft.Jet.OLEDB.3.51;"
L"Data Source=Elections.mdb","","",0);
// открываем таблицу
_RecordsetPtr rset("ADODB.Recordset");
rset->Open(L"ElectTbl",(IDispatch*)con,
adOpenDynamic,adLockOptimistic,adCmdTable);
FieldsPtr flds = rset->Fields;
// добавляем
rset->AddNew();
flds->Item[L"Фамилия"] ->Value = L"Пупкин";
flds->Item[L"Имя"] ->Value = L"Василий";
flds->Item[L"Отчество"] ->Value = L"Карлович";
flds->Item[L"Голосовал ли"] ->Value = false;
flds->Item[L"За кого проголосовал"]->Value = L"Против всех";
rset->Update();
// подменяем
flds->Item[L"Голосовал ли"] ->Value = true;
flds->Item[L"За кого проголосовал"]->Value = L"За наших";
rset->Update();
// просмотр
rset->MoveFirst();
while (!rset->ADOEOF) {
char buf[1024];
sprintf(buf,"%s %s %s: %s - %s\n",
(LPCTSTR)_bstr_t(flds->Item[L"Фамилия"]->Value),
(LPCTSTR)_bstr_t(flds->Item[L"Имя"]->Value),
(LPCTSTR)_bstr_t(flds->Item[L"Отчество"]->Value),
(bool)flds->Item[L"Голосовал ли"]->Value? "Да": "Нет",
(LPCTSTR)_bstr_t(flds->Item[L"За кого проголосовал"]->Value));
CharToOem(buf,buf);
printf(buf);
rset->MoveNext();
}
} catch (_com_error& er) {
char buf[1024];
sprintf(buf,"_com_error:\n"
"Error : %08lX\n"
"ErrorMessage: %s\n"
"Description : %s\n"
"Source : %s\n",
er.Error(),
(LPCTSTR)_bstr_t(er.ErrorMessage()),
(LPCTSTR)_bstr_t(er.Description()),
(LPCTSTR)_bstr_t(er.Source()));
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, большую часть
содержимого которых нам придётся удалить. После
всех изменений эти файлы будут иметь следующий
вид:
msflexgrid.h
// msflexgrid.h
#ifndef __MSFLEXGRID_H__
#define __MSFLEXGRID_H__
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#pragma warning(disable:4146)
#import <MSFLXGRD.OCX>
class CMSFlexGrid : public CWnd
{
protected:
DECLARE_DYNCREATE(CMSFlexGrid)
public:
MSFlexGridLib::IMSFlexGridPtr I; // доступ к интерфейсу
void PreSubclassWindow (); // инициализация I
};
//{{AFX_INSERT_LOCATION}}
#endif
msflexgrid.cpp
// msflexgrid.cpp
#include "stdafx.h"
#include "msflexgrid.h"
IMPLEMENT_DYNCREATE(CMSFlexGrid, CWnd)
void CMSFlexGrid::PreSubclassWindow ()
{
CWnd::PreSubclassWindow();
MSFlexGridLib::IMSFlexGrid *pInterface = NULL;
if (SUCCEEDED(GetControlUnknown()->QueryInterface(I.GetIID(),
(void**)&pInterface))) {
ASSERT(pInterface != NULL);
I.Attach(pInterface);
}
}
Теперь вставим элемент в любой диалог, например
CAboutDlg. В диалог добавим переменную связанную
с классом CMSFlexGrid и метод OnInitDialog, текст
которого приведён ниже. При вызове диалога в наш
FlexGrid будут добавлены два элемента:
BOOL CAboutDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_grid.I->AddItem("12345");
m_grid.I->AddItem("54321");
return TRUE;
}
В заключении, позволю себе высказать ещё
несколько замечаний.
- Всегда внимательно изучайте файлы *.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 = _com_dispatch_propget(this,0x01,VT_BSTR,&_result);
// HRESULT _hr = get_ConnectionString(&_result);
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
|