Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Сабуров С.В. - Язык программирования C и C++ - 2006

.pdf
Скачиваний:
312
Добавлен:
13.08.2013
Размер:
1.42 Mб
Скачать

Трюки программирования

Первое на что следует обратить внимание — это на строчку файла 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 самостоятельно генерирует

467

Трюки программирования

определение 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");

или

468

Трюки программирования

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 генерирует исключения. Это происходит всегда при выполнении следующего условия:

469

Трюки программирования

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"

470

Трюки программирования

#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, заменив такое имя на любое другое.

471

Трюки программирования

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"

472

Трюки программирования

"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++ такое, естественно, не пройдёт.

//заполняем ячейки

473

Трюки программирования

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();

//делаем красиво

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.

474

Трюки программирования

#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();

475

Трюки программирования

// просмотр 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: Menu Project Add To Project Components and Controls Registered ActiveX Controls.

476

Соседние файлы в предмете Программирование на C++