Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Программы Обои   Экспорт RSS E-Books
 
 

   Программирование -> C / C++ -> Использование директивы #import в Visual C++


Использование директивы #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 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< 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       : %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("I","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("I","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"]->->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

 

 
Интересное в сети
 
10 новых программ
CodeLobster PHP Edition 3.7.2
WinToFlash 0.7.0008
Free Video to Flash Converter 4.7.24
Total Commander v7.55
aTunes 2.0.1
Process Explorer v12.04
Backup42 v3.0
Predator 2.0.1
FastStone Image Viewer 4.1
Process Lasso 3.70.4
FastStone Image Viewer 4.0
Xion Audio Player 1.0.125
Notepad GNU v.2.2.8.7.7
K-Lite Codec Pack 5.3.0 Full


Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
Реклама на сайте. Размещая рекламу у нас, вы получите новых посетителей, которые могут стать вашими клиентами.
 
Это интересно
 

Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.