Языки программирования С, С++
.pdfТрюки программирования
Первое на что следует обратить внимание — это на строчку файла 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