Использование директивы #import в Visual 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 имеется несколько.

  • Первый из них - 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));

 

Читайте также:
  • Работа с СУБД Oracle через интерфейс OCCI
    Принятые именования объектов Environment* env;Connection* conn;Statement* stmt;ResultSet* rs;SQLException &sqlExcp; Давайте рассмотрим пример небольшой тестовой программы использующей интерфейс OCCI:#include #include "occi.h"  using namespace std;using namespace oracle::occi;#defi...
  • Задача Майхилла для Microsoft Visual C++
    Алгоритм поведения и автоматная модель стрелка На первый окрик: "Кто идет?" - он стал шутить,На выстрел в воздух закричал: "Кончай дурить!"Я чуть замешкался и, не вступая в спор,Чинарик выплюнул - и выстрелил в упор.В. Высоцкий В задаче Майхилла необходимо определить, как нужно...
  • Использование ODBC в Visual C++
    Установка соединения Для работы необходимо включить описание функций, которые находятся в файле afxdb.h: // пример#include "afxdb.h" Для начала работы необходимо используя класс создать тект используя конструктор. В конструктор нет необходимости передавать параметры. После создания обье...
  • Создание простого приложения с плагинами
    Динамически подключаемые модули (DLL) - это модули, которые содержат функции и данные. Эти модули загружаются во время выполнения программы, использующей эти модули (хоста). В ОС Windows модули содержат внутренние и экспортируемые функции (в UNIX подобных системах все функции являются экспортируе...
  • Работа с 1C Предприятие из Visual C++
    1С Предприятие предоставляем пользователям механизм OLE DB. Если Вам вдруг захотелось использовать какие либо данные из 1С Предприятия в вашей программе – вы можете воспользоваться этим механизмом. Совсем просто это делается в таких языках, как Visual Basic или Delphi. В них вся работа с O...