| 
Использование директивы #import в Visual C++
Игорь Ткачёв, cpplink.nm.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 Debugsampl.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 "debugsampl.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 Debugsampl.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< color=red>->Method(1l,L"12345");
    obj< color=red>->Prop = SAMPLLib::SamplType2;
    obj2< color=red>->Prop = obj< color=red>->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       : %08lXn"
               "ErrorMessage: %sn"
               "Description : %sn"
               "Source      : %sn",
               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 FilesMicrosoft OfficeOfficeMSO9.DLL"
#import "C:Program FilesCommon FilesMicrosoft SharedVBAVBA6VBE6EXT.OLB"
#import "C:Program FilesMicrosoft OfficeOfficeMSWORD9.OLB" 
        rename("ExitWindows","_ExitWindows")
#import "C:Program FilesMicrosoft OfficeOfficeEXCEL9.OLB" 
        rename("DialogBox","_DialogBox") 
        rename("RGB","_RGB") 
        exclude("I","IPicture")
#import "C:Program FilesCommon FilesMicrosoft SharedDAODAO360.DLL" 
        rename("EOF","EndOfFile") rename("BOF","BegOfFile")
#import "C:Program FilesMicrosoft OfficeOfficeMSACC9.OLB"
#else
// for MS Office 97
#import "C:Program FilesMicrosoft OfficeOfficeMSO97.DLL"
#import "C:Program FilesCommon FilesMicrosoft SharedVBAVBEEXT1.OLB"
#import "C:Program FilesMicrosoft OfficeOfficeMSWORD8.OLB" 
        rename("ExitWindows","_ExitWindows")
#import "C:Program FilesMicrosoft OfficeOfficeEXCEL8.OLB" 
        rename("DialogBox","_DialogBox") 
        rename("RGB","_RGB") 
        exclude("I","IPicture")
#import "C:Program FilesCommon FilesMicrosoft SharedDAODAO350.DLL" 
        rename("EOF","EndOfFile")
        rename("BOF","BegOfFile")
#import "C:Program FilesMicrosoft OfficeOfficeMSACC8.OLB"
#endif
 
Этот файл содержит подключение библиотек типов MS Word,
MS Excel и MS Access. По умолчанию подключаются библиотеки
для MS Office 2000, если на вашем компьютере установлен
MS Office 97, то следует закомментировать строчку
#define Uses_MSO2000
 
Если MS Office установлен в каталог отличный от
"C:Program FilesMicrosoft OfficeOffice", то пути к библиотекам
также следует подкорректировать. Обратите внимание на атрибут rename,
его необходимо использовать, когда возникают конфликты имён свойств и методов
библиотеки типов с препроцессором. Например, функция ExitWindows
объявлена в файле winuser.h как макрос:
#define ExitWindows(dwReserved,Code) ExitWindowsEx(EWX_LOGOFF,0xFFFFFFFF)
 
В результате, там, где препроцессор встретит имя ExitWindows, он будет
пытаться подставлять определение макроса. Этого можно избежать при
использовании атрибута rename, заменив такое имя на любое другое.
  
// 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       : %08lXn"
                "ErrorMessage: %sn"
                "Description : %sn"
                "Source      : %sn",
                er.Error(),
                (LPCTSTR)_bstr_t(er.ErrorMessage()),
                (LPCTSTR)_bstr_t(er.Description()),
                (LPCTSTR)_bstr_t(er.Source()));
    CharToOem(buf,buf); // только для косольных приложений
    printf(buf);
  }
  ::CoUninitialize();
}
 
// 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"]->->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       : %08lXn"
                "ErrorMessage: %sn"
                "Description : %sn"
                "Source      : %sn",
                er.Error(),
                (LPCTSTR)_bstr_t(er.ErrorMessage()),
                (LPCTSTR)_bstr_t(er.Description()),
                (LPCTSTR)_bstr_t(er.Source()));
    CharToOem(buf,buf); // только для косольных приложений
    printf(buf);
  }
  ::CoUninitialize();
}
 
// console.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <stdio.h>
#import "C:Program FilesCommon FilesSystemadomsado20.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 - %sn",
              (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       : %08lXn"
                "ErrorMessage: %sn"
                "Description : %sn"
                "Source      : %sn",
                er.Error(),
                (LPCTSTR)_bstr_t(er.ErrorMessage()),
                (LPCTSTR)_bstr_t(er.Description()),
                (LPCTSTR)_bstr_t(er.Source()));
    CharToOem(buf,buf); // только для косольных приложений
    printf(buf);
  }
  ::CoUninitialize();
}
 
Для этого примера нам понадобится любое оконное приложение. 
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
 
 
 |