Сразу внесу некоторую поправку к обозначенной в заголовке теме. В этой статье речь пойдёт не просто о COM и ActiveX, а о COM и ActiveX библиотеках, содержащих библиотеки типов (type library).
Поводом для написания этой статьи стали мои собственные изыскания в этой теме. На просторах Интернета тут и там разбросана информация о работе с COM и ActiveX из Visual C++ (отмечу, что речь идёт не о .NET, а обычном C++), но хорошего, структурированного материала мне найти не удалось. В этой статье я разберу два примера: в первом я расскажу о работе с простыми COM библиотеками; во втором будет показана работа с объектом ActiveX. Я не буду рассказывать, что такое COM, ActiveX и Type libraries, об этом Вы сможете прочитать, например, здесь.
У директивы #import есть множество дополнительных опций, о которых Вы сможете прочитать тут или в справочной системе к Visual Studio.
Но вот тут, на первый взгляд, может возникнуть проблема. Где взять библиотеку типов, если большинство серверов распространяется одним файлом (чаще всего это либо *.dll, либо *.ocx)? Ответ прост; библиотека типов является ни чем иным как ресурсом это сервера. А если это ресурс, то получить его можно, к примеру, с помощью Resource Hacker. На скриншоте ниже показано, как это сделать.

Директива #import помещает весь сгенерированный код в пространство имён, соответствующее имени библиотеки, в нашем случае – это MSXML2. В сгенерированный код, по умолчанию, добавляются объявления "умных" указателей для каждого интерфейса. Имена их соответствуют именам интерфейсов с постфиксом Ptr. Объявив такой объект, мы можем передать указатель на него в функцию CoCreateInstance.
Каждый интерфейс и класс объявляется в файлах *.tlh специальным образом, например
Обратите внимание, я не вызываю метод Release из xml_document и xml_elem, это за меня делают "умные" указатели.
Для демонстрации встраивания элемента управления, я воспользовался библиотекой MSFLXGRD.OCX, которая входит в стандартную поставку Visual Basic 6.0. То, что у меня получилось, видно на следующем скриншоте.

По традиции, сразу приведу листинг программы, а затем прокомментирую.
Операции Create замещают операции класса CWnd и создают контрол из переданных в конструктор GUID’ов интерфейса и кокласса. Член mp_ax инициализируется указателем из элемента управления и является мостом между клиентом и интерфейсом COM.
Далее окно ActiveX создаётся как и все другие окна, с той лишь разницей, что в конструктор мы передаём GUID’ы класса и интерфейса.
Следует заметить, что директива #import генерирует специальные поля классов – свойства. Такие поля также являются расширением Visual C++ и выглядят следующим образом
Чтобы окно ActiveX могло общаться с окружающим миром посредствам сообщений, библиотекой MFC предусмотрена специальная карта сообщений. Используется она практически идентично карте основных сообщений. Для её объявления добавляем в класс родительского окна макроопределение
Чтобы добавить обработку события в карту EVENTSINK_MAP следует добавить в класс окна функцию с идентичной сигнатурой (за исключением имени). После этого в карту событий помещается элемент с именем ON_EVENT. Первым параметром этого макроса является класс, принимающей событие, второй - идентификатор ActiveX объекта, третий - это номер события, его можно подсмотреть в реализации методов-событий в файле *.tli. Стандартные события имеют макроимена. Следующим параметром мы передаём метод, который будет вызван в ответ на событие. Последний параметр - это набор аргументов, которым соответствуют макроопределения. Набор этих параметров пишется без разделения запятой. Все возможные значения объявлены в файле afxdisp.h.
Для примера, я определил обработчик события Click. Теперь, при щелчке мышью, в соответствующую ячейку будут устанавливаться числовые значения, каждый раз на единицу больше предыдущего.
Следует отметить, что для работы с COM в приложении MFC необходимо вызвать функцию AfxOleInit, а для работы с компонентами ActiveX - функцию AfxEnableControlContainer. Все они находятся в файле afxdisp.h, но подключать их следует из afxole.h.
Поводом для написания этой статьи стали мои собственные изыскания в этой теме. На просторах Интернета тут и там разбросана информация о работе с COM и ActiveX из Visual C++ (отмечу, что речь идёт не о .NET, а обычном C++), но хорошего, структурированного материала мне найти не удалось. В этой статье я разберу два примера: в первом я расскажу о работе с простыми COM библиотеками; во втором будет показана работа с объектом ActiveX. Я не буду рассказывать, что такое COM, ActiveX и Type libraries, об этом Вы сможете прочитать, например, здесь.
Директива препроцессора #import
Компилятор Microsoft Visual C++ определяет директиву препроцессора #import. Основное её предназначение – загружать информацию из библиотеки типов и представлять её в виде кода C++. В простейшем случае директива #import используется так#import "libname.tlb"
После компиляции в каталоге сборки программы появляются два файла: libname.tlh и libname.tli. Первый из них является заголовочным и автоматически подключается к программе, а второй содержит код реализации и компилируется вместе с проектом. У директивы #import есть множество дополнительных опций, о которых Вы сможете прочитать тут или в справочной системе к Visual Studio.
Где взять библиотеку типов
Хотя директива #import и позволяет подключать сами COM серверы, я советую подключать именно библиотеки типов, это бинарные файлы, обычно имеющие расширение TLB. Это позволит добавить такой файл в Ваш проект и не заботиться о расположении библиотеки при сборке проекта на другом компьютере.Но вот тут, на первый взгляд, может возникнуть проблема. Где взять библиотеку типов, если большинство серверов распространяется одним файлом (чаще всего это либо *.dll, либо *.ocx)? Ответ прост; библиотека типов является ни чем иным как ресурсом это сервера. А если это ресурс, то получить его можно, к примеру, с помощью Resource Hacker. На скриншоте ниже показано, как это сделать.

Простой COM сервер
Начнём работу с простого примера, в котором не нужно отображать визуальные компоненты. Для демонстрации я взял библиотеку msxml6.dll. Стоит отметить, что в Windows API существует привязка к этой библиотеке. Ниже приведён листинг программы, которая создаёт файл "test.xml" с одним лишь тегом "Example".#include <iostream>
#import "msxml6.tlb"
class CComInitializer
{
public:
CComInitializer() { ::CoInitialize(NULL); }
~CComInitializer() { ::CoUninitialize(); }
} gComInitializer;
int main()
{
MSXML2::IXMLDOMDocument3Ptr xml_document;
HRESULT hr = ::CoCreateInstance(__uuidof(MSXML2::DOMDocument60),
NULL, CLSCTX_INPROC_SERVER, __uuidof(MSXML2::IXMLDOMDocument3),
reinterpret_cast<void **>(&xml_document));
if(SUCCEEDED(hr))
{
MSXML2::IXMLDOMElementPtr xml_elem =
xml_document->createElement(L"Example");
xml_document->appendChild(xml_elem);
xml_document->save(L"test.xml");
}
else
std::cerr << "Error creating instancen";
return 0;
}
Назначение класса CComInitializer только в том, чтобы инициализировать модель COM в приложении. Для этого создаётся один глобальный объект этого класса. При разрушении объекта модель COM деинициализируется.Директива #import помещает весь сгенерированный код в пространство имён, соответствующее имени библиотеки, в нашем случае – это MSXML2. В сгенерированный код, по умолчанию, добавляются объявления "умных" указателей для каждого интерфейса. Имена их соответствуют именам интерфейсов с постфиксом Ptr. Объявив такой объект, мы можем передать указатель на него в функцию CoCreateInstance.
Каждый интерфейс и класс объявляется в файлах *.tlh специальным образом, например
struct __declspec(uuid("2933bf96-7b36-11d2-b20e-00c04f983e60"))
IXMLDOMDocument3 : IXMLDOMDocument2
Это позволяет получить их GUID’ы с помощью оператора (специфичного для Visual C++) __uuidof. Для того, чтобы быть точно уверенными, что Вы получаете GUID именно того класса, который Вам нужен, следует обратиться к текстам библиотеки типов. Чтобы получить текст библиотеки из двоичного файла можно воспользоваться утилитой OleView или просмоторщиком из Total Commander. В IDL коде библиотеки мы должны найти coclass, который реализует нужный интерфейс.[
uuid(88D96A05-F192-11D4-A65F-0040963251E5),
helpstring("W3C-DOM XML Document 6.0 (Apartment)")
]
coclass DOMDocument60 {
[default] interface IXMLDOMDocument3;
[default, source] dispinterface XMLDOMDocumentEvents;
};
Все последующие действия специфичны для библиотеки MSXML и приведены только для примера.Обратите внимание, я не вызываю метод Release из xml_document и xml_elem, это за меня делают "умные" указатели.
Элемент управления ActiveX
Для того, чтобы разместить элемент ActiveX в окне требуется выполнить очень много скучной рутинной работы, поэтому мы воспользуемся готовым решением, предоставляемым библиотекой MFC. Те, кому интересно узнать всю подноготную, могут пройти по этой и этой ссылкам.Для демонстрации встраивания элемента управления, я воспользовался библиотекой MSFLXGRD.OCX, которая входит в стандартную поставку Visual Basic 6.0. То, что у меня получилось, видно на следующем скриншоте.

По традиции, сразу приведу листинг программы, а затем прокомментирую.
#define WINVER 0x0501
#include <afxwin.h>
#include <afxole.h>
#import "msflexgrid.tlb"
template<typename InterfaceT>
class CComWindow :
public CWnd
{
public:
CComWindow(const IID & iid, const CLSID & class_id) :
mp_ax(NULL),
m_class_id(class_id),
m_iid(iid)
{
}
virtual ~CComWindow()
{
if(NULL != mp_ax)
mp_ax->Release();
}
virtual BOOL Create(LPCTSTR class_name, LPCTSTR window_name,
DWORD style, const RECT & rect, CWnd * parent, UINT id,
CCreateContext * context = NULL)
{
UNREFERENCED_PARAMETER(class_name);
UNREFERENCED_PARAMETER(context);
BOOL result = CreateControl(m_class_id, window_name,
style, rect, parent, id);
if(result)
result = InitComponent();
return result;
}
virtual BOOL Create(LPCTSTR window_name, DWORD style,
const RECT & rect, CWnd * parent, UINT id, CFile * persist = NULL,
BOOL storage = FALSE, BSTR lic_key = NULL)
{
BOOL result = CreateControl(m_class_id, window_name, style,
rect, parent, id, persist, storage, lic_key);
if(result)
result = InitComponent();
return result;
}
protected:
BOOL InitComponent()
{
BOOL result = FALSE;
COleControlSite * control_site = GetControlSite();
if(NULL != control_site)
{
HRESULT hr =
control_site->m_pInPlaceObject->QueryInterface(
m_iid, reinterpret_cast<void **>(&mp_ax));
if(SUCCEEDED(hr))
result = TRUE;
else
mp_ax = NULL;
}
return result;
}
public:
InterfaceT * mp_ax;
protected:
CLSID m_class_id;
IID m_iid;
}; // class CComWindow
class CMainWindow :
public CWnd
{
DECLARE_MESSAGE_MAP()
DECLARE_EVENTSINK_MAP()
public:
CMainWindow();
virtual ~CMainWindow();
int OnCreate(LPCREATESTRUCT cs);
HRESULT OnFlexGridClick();
private:
CComWindow<MSFlexGridLib::IMSFlexGrid> * mp_flex_grid;
static const UINT m_grid_id = 1800;
}; // class CMainWindow
BEGIN_MESSAGE_MAP(CMainWindow, CWnd)
ON_WM_CREATE()
END_MESSAGE_MAP()
BEGIN_EVENTSINK_MAP(CMainWindow, CWnd)
ON_EVENT(CMainWindow, m_grid_id, DISPID_CLICK,
CMainWindow::OnFlexGridClick, VTS_NONE)
END_EVENTSINK_MAP()
CMainWindow::CMainWindow() :
mp_flex_grid(NULL)
{
}
CMainWindow::~CMainWindow()
{
delete mp_flex_grid;
}
int CMainWindow::OnCreate(LPCREATESTRUCT cs)
{
int result = CWnd::OnCreate(cs);
if(0 == result)
{
mp_flex_grid = new CComWindow<MSFlexGridLib::IMSFlexGrid>(
__uuidof(MSFlexGridLib::IMSFlexGrid),
__uuidof(MSFlexGridLib::MSFlexGrid));
BOOL created = mp_flex_grid->Create(L"",
WS_CHILD | WS_VISIBLE, CRect(CPoint(10, 10), CSize(520, 450)),
this, m_grid_id);
if(created)
{
const long cols = 8;
const long rows = 25;
mp_flex_grid->mp_ax->Cols = cols;
mp_flex_grid->mp_ax->Rows = rows;
mp_flex_grid->mp_ax->ColWidth[0] = 350;
mp_flex_grid->mp_ax->Col = 0;
wchar_t text[8];
for(long i = 1; i < rows; ++i)
{
_itow(i, text, 10);
mp_flex_grid->mp_ax->Row = i;
mp_flex_grid->mp_ax->Text = text;
}
mp_flex_grid->mp_ax->Row = 0;
mp_flex_grid->mp_ax->Col = 1;
mp_flex_grid->mp_ax->Text = L"Пн";
mp_flex_grid->mp_ax->Col = 2;
mp_flex_grid->mp_ax->Text = L"Вт";
mp_flex_grid->mp_ax->Col = 3;
mp_flex_grid->mp_ax->Text = L"Ср";
mp_flex_grid->mp_ax->Col = 4;
mp_flex_grid->mp_ax->Text = L"Чт";
mp_flex_grid->mp_ax->Col = 5;
mp_flex_grid->mp_ax->Text = L"Пт";
mp_flex_grid->mp_ax->Col = 6;
mp_flex_grid->mp_ax->Text = L"Сб";
mp_flex_grid->mp_ax->Col = 7;
mp_flex_grid->mp_ax->Text = L"Вс";>
}
else
result = 1;
}
return result;
}
HRESULT CMainWindow::OnFlexGridClick()
{
static int i_text = 1;
wchar_t text[16];
::_itow(i_text++, text, 10);
mp_flex_grid->mp_ax->Text = text;
return S_OK;
}
class CApplication :
public CWinApp
{
public:
CApplication();
virtual ~CApplication();
virtual BOOL InitInstance();
} gApplication; // class CApplication
CApplication::CApplication()
{
}
CApplication::~CApplication()
{
delete m_pMainWnd;
}
BOOL CApplication::InitInstance()
{
BOOL result = CWinApp::InitInstance();
if(FALSE != result)
{
::AfxOleInit();
::AfxEnableControlContainer();
const wchar_t * wnd_class = ::AfxRegisterWndClass(
0, LoadStandardCursor(IDC_ARROW),
reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
CMainWindow * main_window = new CMainWindow();
main_window->CreateEx(0, wnd_class, L"Flex Grid Example",
WS_OVERLAPPEDWINDOW, 200, 100, 800, 600,
GetDesktopWindow(), NULL);
m_pMainWnd = main_window;
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
}
return result;
}
Теперь, нам требуется не просто получить указатель на интерфейс, но и связать его с окном, поэтому создаём класс CComWindow, сделав его шаблонным для универсальности. В качестве параметра шаблона требуется указывать тип интерфейса COM объекта.Операции Create замещают операции класса CWnd и создают контрол из переданных в конструктор GUID’ов интерфейса и кокласса. Член mp_ax инициализируется указателем из элемента управления и является мостом между клиентом и интерфейсом COM.
Далее окно ActiveX создаётся как и все другие окна, с той лишь разницей, что в конструктор мы передаём GUID’ы класса и интерфейса.
Следует заметить, что директива #import генерирует специальные поля классов – свойства. Такие поля также являются расширением Visual C++ и выглядят следующим образом
__declspec(property(get=GetRows,put=PutRows))
long Rows;
где значениями параметров get и put являются функции, которые неявно вызываются при соответствующих обращениях к данному полю.Чтобы окно ActiveX могло общаться с окружающим миром посредствам сообщений, библиотекой MFC предусмотрена специальная карта сообщений. Используется она практически идентично карте основных сообщений. Для её объявления добавляем в класс родительского окна макроопределение
DECLARE_EVENTSINK_MAP()
А определение этой карты должно находиться между макросамиBEGIN_EVENTSINK_MAP(CMainWindow, CWnd)
иEND_EVENTSINK_MAP()
Далее, нам нужно в IDL тексте TLB файла найти, какой класс реализует события интересующего интерфейса[
uuid(6262D3A0-531B-11CF-91F6-C2863C385E30),
helpstring("Microsoft FlexGrid Control 6.0"),
helpcontext(0x00059621),
licensed,
control
]
coclass MSFlexGrid {
[default] interface IMSFlexGrid;
[default, source] dispinterface DMSFlexGridEvents;
};
В нашем случае – это DMSFlexGridEvents. В *.tlh файле Вы найдёте объявление этого класса со всеми доступными событиями, а в файле *.tli – все реализации.Чтобы добавить обработку события в карту EVENTSINK_MAP следует добавить в класс окна функцию с идентичной сигнатурой (за исключением имени). После этого в карту событий помещается элемент с именем ON_EVENT. Первым параметром этого макроса является класс, принимающей событие, второй - идентификатор ActiveX объекта, третий - это номер события, его можно подсмотреть в реализации методов-событий в файле *.tli. Стандартные события имеют макроимена. Следующим параметром мы передаём метод, который будет вызван в ответ на событие. Последний параметр - это набор аргументов, которым соответствуют макроопределения. Набор этих параметров пишется без разделения запятой. Все возможные значения объявлены в файле afxdisp.h.
Для примера, я определил обработчик события Click. Теперь, при щелчке мышью, в соответствующую ячейку будут устанавливаться числовые значения, каждый раз на единицу больше предыдущего.
Следует отметить, что для работы с COM в приложении MFC необходимо вызвать функцию AfxOleInit, а для работы с компонентами ActiveX - функцию AfxEnableControlContainer. Все они находятся в файле afxdisp.h, но подключать их следует из afxole.h.