Використання директиви # import в Visual C + +, C / C + +, Програмування, статті

У даній статті я спробую пояснити те, як працює ця директива і навести кілька прикладів її використання. Сподіваюся, після цього ви теж знайдете її корисною. Директива #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 є декілька.



Останній спосіб доступу до об’єктів 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. До таких класів відносяться наступні.



Нам залишилося уточнити природу класу 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:

“Error : %08lX

“ErrorMessage: %s

“Description : %s

“Source : %s
“,
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, Замінивши таке ім’я на будь-яке інше.


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 est.htm”),
&_variant_t(long(wdFormatHTML)));
/ / Іноді доведеться вдаватися до явного перетворення типів,
/ / Т.к. оператор перетворення char * в VARIANT * не визначено

/ / Відкриває документ test.doc
_DocumentPtr wdoc2 = word->Documents->Open(&_variant_t(“C:MyDoc est.doc”));
/ / Викликаємо макрос
word->Run(“Macro1”);

} catch (_com_error& er) {
char buf[1024];
sprintf(buf,”_com_error:

“Error : %08lX

“ErrorMessage: %s

“Description : %s

“Source : %s
“,
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:

“Error : %08lX

“ErrorMessage: %s

“Description : %s

“Source : %s
“,
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 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 – %s
“,
(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:

“Error : %08lX

“ErrorMessage: %s

“Description : %s

“Source : %s
“,
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;
}


Висновок


У висновку, дозволю собі висловити ще кілька зауважень.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*