Здорова ребятки!
В Этом посте я буду разбирать макросы MFC, там очень много разных макросов, которые не понятные зачем они используются и так далее. Пока что я разобрал два макроса IMPLEMENT_DYNCREATE и DECLARE_DYNCREATE начнем с них.
IMPLEMENT_DYNCREATE и DECLARE_DYNCREATE
Эти макросы используются для динамического создания объектов и так начнем с определения макроса IMPLEMENT_DYNCREATE
1 2 3 4 5 |
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \ CObject* PASCAL class_name::CreateObject() \ { return new class_name; } \ IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \ class_name::CreateObject, NULL) |
как мы видим создается функция CreateObject(), посмотрим определение макроса PASCAL:
1 |
#define PASCAL __stdcall |
Это просто знак для компилятора «Соглашение вызова». Дальше посмотрим определение макроса IMPLEMENT_RUNTIMECLASS:
1 2 3 4 5 6 |
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \ AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \ #class_name, sizeof(class class_name), wSchema, pfnNew, \ RUNTIME_CLASS(base_class_name), NULL, class_init }; \ CRuntimeClass* class_name::GetRuntimeClass() const \ { return RUNTIME_CLASS(class_name); } |
как мы видим здесь создается определение функция GetRuntimeClass и создается инициализируется структура CRuntimeClass. В нашем классе должен быть член класса с названием class_name::class##class_name. Посмотрим определение макроса AFX_COMBAT:
1 |
#define AFX_COMDAT __declspec(selectany) |
Это какаето фигня, вот ссылка, можете почитать что там пишут по этому поводу. Смотрим определение макроса RUNTIME_CLASS:
1 |
#define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name) |
ну и смотрим макросс _RUNTIME_CLASS:
1 |
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name)) |
тут возвращается указатель на class_name::class##class_name, он должен находится в нашем классе.
Теперь давайте напишем какой реальный код создается при вызове макроса IMPLEMENT_DYNCREATE
1 |
IMPLEMENT_DYNCREATE(CSpaceship,CCmdTarget); |
CSpaceship — это класс порожденный от ССmdTarget, CCmdTarget- это базовый класс класса CSpaceship. И в итоге этот макрос создаст следующий код:
1 2 3 4 |
CObject* PASCAL CSpaceship::CreateObject() { return new CSpaceship; } IMPLEMENT_RUNTIMECLASS(CSpaceship, CCmdTarget, 0xFFFF, CSpaceship::CreateObject, NULL) |
как видим появилось определение первой функция CSpaceship::CreateObject, дальше вызывается макрос IMPLEMENT_RUNTIMECLASS, запишем что в результате этого вызова мы получим:
1 2 3 4 5 |
AFX_COMDAT const CRuntimeClass CSpaceship::classCSpaceship = { #CSpaceship, sizeof(class CSpaceship), 0xFFFF, CSpaceship::CreateObject, RUNTIME_CLASS(CCmdTarget), NULL, NULL }; CRuntimeClass* CSpaceship::GetRuntimeClass() const { return RUNTIME_CLASS(CSpaceship); } |
здесь мы видим что идет инициализация структуры CRuntimeClass CSpaceship::classCspaceship — это статический член получается класса CSpaceship. и создается определение функции CSpaceship::GetRuntimeClass. И давайте теперь последний штрих заменим макрос RUNTIME_CLASS:
1 2 3 4 5 6 7 8 9 |
CObject* PASCAL CSpaceship::CreateObject() { return new CSpaceship; } AFX_COMDAT const CRuntimeClass CSpaceship::classCSpaceship = { #CSpaceship, sizeof(class CSpaceship), 0xFFFF, CSpaceship::CreateObject, ((CRuntimeClass*)(&CCmdTarget::class##CCmdTarget)), NULL, NULL }; CRuntimeClass* CSpaceship::GetRuntimeClass() const { return ((CRuntimeClass*)(&CSpaceship::classCSpaceship)); } |
вот в общем то что мы получили в результате вызова
1 |
IMPLEMENT_DYNCREATE(CSpaceship,CCmdTarget); |
два определения функции и инициализацию переменной.
Дальше разберем макрос DECLARE_DYNCREATE, вот его определение:
1 2 3 |
#define DECLARE_DYNCREATE(class_name) \ DECLARE_DYNAMIC(class_name) \ static CObject* PASCAL CreateObject(); |
создалось объявление функции CreateObject и вызвался макрос DECLARE_DYNAMIC, вот его определение:
1 2 3 4 |
#define DECLARE_DYNAMIC(class_name) \ public: \ static const CRuntimeClass class##class_name; \ virtual CRuntimeClass* GetRuntimeClass() const; \ |
здесь создается статический член на структуру CRuntimeClass и виртуальный метод GetRuntimeClass который возвращает указатель на структуру CRuntimeClass. Давайте запишем как это будет выглядеть для вызова
1 |
DECLARE_DYNCREATE(CSpaceship); |
вот код:
1 2 |
DECLARE_DYNAMIC(CSpaceship) static CObject* PASCAL CreateObject(); |
и еще раз для макроса DECLARE_DYNAMIC:
1 2 3 4 |
public: static const CRuntimeClass classCSpaceship; virtual CRuntimeClass* GetRuntimeClass() const; static CObject* PASCAL CreateObject(); |
отакой код получается создает в классе
1 |
DECLARE_DYNCREATE(CSpaceship); |
и от такой код
1 2 3 4 5 6 7 8 9 |
CObject* PASCAL CSpaceship::CreateObject() { return new CSpaceship; } AFX_COMDAT const CRuntimeClass CSpaceship::classCSpaceship = { #CSpaceship, sizeof(class CSpaceship), 0xFFFF, CSpaceship::CreateObject, ((CRuntimeClass*)(&CCmdTarget::class##CCmdTarget)), NULL, NULL }; CRuntimeClass* CSpaceship::GetRuntimeClass() const { return ((CRuntimeClass*)(&CSpaceship::classCSpaceship)); } |
создается при вызове
1 |
IMPLEMENT_DYNCREATE(CSpaceship,CCmdTarget); |
Приведем несколько реальных примеров использования этих двух макросов IMPLEMENTE_DYNCRATE и DECLARE_DYNCRATE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include <iostream> using std::cout; #include <afxwin.h> class CMy_class : public CObject { public: //есили конструктора нету, то он вроде создается пустой сам по себе // CMy_class(){} void print(){cout <<"class CMy_class\r\n";} //создает две функции: //static CObject* PASCAL CreateObject(); //virtual CSpaceship* GetRuntimeClass() const; //и переменную static const CRuntimeClass classCSpaceship; DECLARE_DYNCREATE(CMy_class) }; //создает определение функций выше /*CObject* PASCAL CMy_class::CreateObject() { return new CMy_class; } AFX_COMDAT const CRuntimeClass CMy_class::classCMy_class = { "CMy_class", sizeof(class CMy_class), 0xFFFF, CMy_class::CreateObject, ((CRuntimeClass*)(&CObject::classCMy_class)), NULL, NULL }; CRuntimeClass* CMy_class::GetRuntimeClass() const { return ((CRuntimeClass*)(&CMy_class::classCMy_class)); } */ IMPLEMENT_DYNCREATE(CMy_class,CObject) int main() { //создаем динамически объект CMy_class* ptr=(CMy_class*)CMy_class::CreateObject(); ptr->print(); //получаем доступ к структуре CRuntimeClass CRuntimeClass* pRun=ptr->GetRuntimeClass(); //m_lpszClassName-имя класса в виде строки заканчивающееся нулем //m_nObjectSize-размер объекта в байтах //m_wSchema-номер версии cout <<pRun->m_lpszClassName<<" - "<<pRun->m_nObjectSize<<" - "<<pRun->m_wSchema<<"\r\n"; return 0; } |
Вывод:
TRACE
И так разберем этот макрос, сразу не сильно понятно что он делает, почитать можно здесь. Так этот макрос работает только при отладке, если мы просто компилируем программу то он компилируется в ничего. Разберем простой примерчик кода:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> using std::cout; #include <afxwin.h> void f(double a, int b) { TRACE("'%d' - Переменная %f\r\n",b,a); } int main() { TRACE("Hellow world!\r\n"); f(0.35, 333); return 0; } |
И вывод:
Вывод идет во вкладку вывод когда мы нажимаем F5 — отладка.
Там еще существует несколько макросов TRACE0,TRACE1,TRACE2 и TRACE3 цифры означают количество параметров. Хз. зачем их использовать, мы будем использовать просто TRACE.
BEGIN_MESSAGE_MAP и END_MESSAGE_MAP и DECLARE_MESSAGE_MAP
Так в общем разберем два этих макроса
Начнем с BEGIN_MESSAGE_MAP, вот ее определение:
1 2 3 4 5 6 7 8 9 10 |
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \ PTM_WARNING_DISABLE \ const AFX_MSGMAP* theClass::GetMessageMap() const \ { return GetThisMessageMap(); } \ const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \ { \ typedef theClass ThisClass; \ typedef baseClass TheBaseClass; \ static const AFX_MSGMAP_ENTRY _messageEntries[] = \ { |
Как мы видим создается определение функция theClass::GetMessageMap и определение второй функции theClass::GetThisMessageMap. Давайте выведем все остальные макросы которые есть в объявлении макроса выше. PTM_WARNING_DISABE:
1 2 3 |
#define PTM_WARNING_DISABLE \ __pragma(warning( push )) \ __pragma(warning( disable : 4867 )) |
тут ничего не ясно что это за фигня, про __pragma можно тут почитать. Смотрим определение AFX_MSGMAP:
1 2 3 4 5 |
struct AFX_MSGMAP { const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)(); const AFX_MSGMAP_ENTRY* lpEntries; }; |
Это оказывается структура, и посмотрим определение AFX_MSGMAP_ENTRY:
1 2 3 4 5 6 7 8 9 |
struct AFX_MSGMAP_ENTRY { UINT nMessage; // windows message UINT nCode; // control code or WM_NOTIFY code UINT nID; // control ID (or 0 for windows messages) UINT nLastID; // used for entries specifying a range of control id's UINT_PTR nSig; // signature type (action) or pointer to message # AFX_PMSG pfn; // routine to call (or special value) }; |
как мы видим это также структура, то есть создается указатель на 2 структуры как бы. Давайте полностью попробуем написать код который генерится при подстановке например этой строки:
1 |
BEGIN_MESSAGE_MAP(CSpaceship,CCmdTarget) |
И получаем мы:
1 2 3 4 5 6 7 8 9 10 |
__pragma(warning( push )) __pragma(warning( disable : 4867 )) const AFX_MSGMAP* CSpaceship::GetMessageMap() const { return GetThisMessageMap(); } const AFX_MSGMAP* __stdcall CSpaceship::GetThisMessageMap() { typedef CSpaceship ThisClass; typedef CCmdTarget TheBaseClass; static const AFX_MSGMAP_ENTRY _messageEntries[] = { |
как бы такой вроде код создается. как видно идет определение одной функции CSpaceship::GetMessageMap и начинается начало определения второй функции CSpaceship::GetThisMessageMap
Разберем макрос END_MESSAGE_MAP, вот его определение:
1 2 3 4 5 6 7 8 |
#define END_MESSAGE_MAP() \ {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \ }; \ static const AFX_MSGMAP messageMap = \ { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ return &messageMap; \ } \ PTM_WARNING_RESTORE |
по коду видно что в скобках передаются какие то параметры видимо доинициализируют структуру AFX_MSGMAP_ENTRY, а также создается статическая конст переменная на AFX_MSGMAP под названием messageMap, ей присваиваются два указателя в скобках TheBaseClass::GetThisMessageMap и _messageEntries[0] — указатель на первый элемент. и возвращается указатель на messageMap — указатель на структуру.
Давайте теперь все вместе запишем что у нас получается
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
__pragma(warning( push )) __pragma(warning( disable : 4867 )) const AFX_MSGMAP* CSpaceship::GetMessageMap() const { return GetThisMessageMap(); } const AFX_MSGMAP* __stdcall CSpaceship::GetThisMessageMap() { typedef CSpaceship ThisClass; typedef CCmdTarget TheBaseClass; static const AFX_MSGMAP_ENTRY _messageEntries[] = { //здесь записываем наши функции //конец функций {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; static const AFX_MSGMAP messageMap = { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; return &messageMap; } PTM_WARNING_RESTORE |
И наконец разберем DECLARE_MESSAGE_MAP
Этот макрос добавляется непосредственно в сам класс и он добавляет к классу объявление функций:
1 2 3 4 |
#define DECLARE_MESSAGE_MAP() \ protected: \ static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \ virtual const AFX_MSGMAP* GetMessageMap() const; \ |
Видно что добавляется определение ptotected: и две функции: статическая GetThisMessageMap; и GetMessageMap обе функции возвращают указатель на структуру AFX_MSGMAP* выще мы нашли ее определение. Вообще глянув на код выще функция GetMessageMap вызывает функцию GetThisMessageMap, GetMessage получается обертка над GetMessageMap.
Посмотрим теперь как используется эти макросы вот код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
BEGIN_MESSAGE_MAP(Ctest_timerDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_TIMER() // реагировать на таймер ON_MESSAGE(WM_MYMESSAGE, OnMyMessage)//собственная функция ON_MESSAGE(WM_THREADENDED,OnThreadEnded) ON_BN_CLICKED(IDC_BUTTON1, &Ctest_timerDlg::OnBnClickedButton1) ON_BN_CLICKED(IDC_BUTTON2, &Ctest_timerDlg::OnBnClickedButton2) ON_BN_CLICKED(IDC_BUTTON3, &Ctest_timerDlg::OnBnClickedButton3) ON_BN_CLICKED(IDC_BUTTON4, &Ctest_timerDlg::OnBnClickedButton4) ON_BN_CLICKED(IDC_BUTTON5, &Ctest_timerDlg::OnBnClickedButton5) END_MESSAGE_MAP() |
я из кода:
1 2 3 4 5 6 7 |
static const AFX_MSGMAP_ENTRY _messageEntries[] = { //здесь записываем наши функции //конец функций {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } }; |
что тут идет инициализация последнего элемента массива _messageEntries[] значением
1 |
{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } |
получается каждый наш макрос: ON_WM_SYSCOMMAND, ON_WM_PAINT, ON_WM_QUERYDRAGICON, и т.д. заменяет на скобки с 6 параметрами для инициализации массива структуры
1 |
static const AFX_MSGMAP_ENTRY _messageEntries[] |
Приведем пример определения например макроса ON_WM_QUERYDRAGICON:
1 2 3 4 |
#define ON_WM_QUERYDRAGICON() \ { WM_QUERYDRAGICON, 0, 0, 0, AfxSig_hv, \ (AFX_PMSG)(AFX_PMSGW) \ (static_cast< HCURSOR (AFX_MSG_CALL CWnd::*)(void) > ( &ThisClass :: OnQueryDragIcon)) }, |
видно что там 6 параметров так же как и у структуры AFX_MSGMAP_ENTRY, шестой параметр — это указатель на функцию. Определение AFX_MSG_CALL:
1 2 3 4 |
#ifndef AFX_MSG_CALL #define AFX_MSG_CALL #endif typedef void (AFX_MSG_CALL CCmdTarget::*AFX_PMSG)(void); |
в общем мы скажем что ничего не ясно что происходит. Ясно одно что эти 3 макроса создают 2 функции одна из которых обертка это GetMessageMap, а вторая GetThisMessageMap создает два typedef на класс и базовый класс с названиями ThisClass и TheBaseClass и производит инициализацию массива static const AFX_MSGMAP_ENTRY _messageEntries[] ={{},{},{},{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }}. Так же там я только щас заметил создается статический указатель на первый элемент массива этими строчками:
1 2 |
static const AFX_MSGMAP messageMap = \ { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \ |
и потом этот указатель на карту возвращается return &messageMap; это похоже на возвтар указателя, значит messageMap — это просто первый элемент массива.
Приведем реальный примерчик использования этих макросов.
В примерчике создается 2 карты сообщений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
#include <afxwin.h> // MFC Основные и стандартные компоненты #define IDC_MYBUTTON 100 // Идентификатор кнопки #define IDC_MYEDIT 102 // Идентификатор поля редактирования class CMyButton: public CButton { public: afx_msg void OnLButtonDblClk(UINT, CPoint); afx_msg void OnRButtonDblClk(UINT, CPoint); private: DECLARE_MESSAGE_MAP(); // таблица откликов кнопки }; void CMyButton::OnLButtonDblClk(UINT, CPoint) { MoveWindow(CRect(120,100,220,150),TRUE); } void CMyButton::OnRButtonDblClk(UINT, CPoint) { MoveWindow(CRect(120,10,220,50),TRUE); } BEGIN_MESSAGE_MAP(CMyButton, CButton) // таблица откликов на сообщения ON_WM_LBUTTONDBLCLK() ON_WM_RBUTTONDBLCLK() END_MESSAGE_MAP() class CMainWnd : public CFrameWnd { public: CMainWnd(); // Конструктор по умолчанию ~CMainWnd();//деструктор afx_msg void OnLButtonDblClk(UINT, CPoint); // виртуальная процедура ответа на левую кнопку afx_msg void OnRButtonDblClk(UINT, CPoint); // виртуальная процедура ответа на правую кнопку afx_msg void OnKeyDown(UINT, UINT, UINT); // виртуальная процедура ответа на клавишу afx_msg void OnButtonDown();//функция вызывающаяся по нажатию на кнопку private: CStatic* MyStatic; // Указатель на объект надпись CMyButton* MyButton; // Указатель на объект кнопка CEdit* MyEdit; // Указатель на объект поле редактирования DECLARE_MESSAGE_MAP(); // таблица откликов }; //карта сообщений создается две функции BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd) // таблица откликов на сообщения ON_WM_LBUTTONDBLCLK() // реакция на нажатие левой кнопки мыши ON_WM_RBUTTONDBLCLK() // реакция на нажатие правой кнопки мышки ON_WM_KEYDOWN() // реакция на нажатие клавиши END_MESSAGE_MAP() void CMainWnd::OnKeyDown(UINT, UINT, UINT) { AfxMessageBox(L" Key Button Down "); } void CMainWnd::OnRButtonDblClk(UINT, CPoint) { AfxMessageBox(L" Rigth Button Click "); } void CMainWnd::OnLButtonDblClk(UINT, CPoint) { AfxMessageBox(L" Left Button Click "); } CMainWnd::CMainWnd() { Create(NULL,L"Step2",WS_OVERLAPPEDWINDOW,rectDefault, NULL,NULL);// Создать окно программы // оператор new по умолчанию в случае ошибки вернет NULL // проверка указателя на NULL дает возможность избавиться от дальнейших ошибок MyStatic = new CStatic(); if (MyStatic!=NULL) MyStatic->Create(L"MyStatic",WS_CHILD|WS_VISIBLE|SS_CENTER, CRect(10,10,100,50),this); // создали MyButton = new CMyButton(); // Меняем класс, на основе которого создается объект if (MyButton!=NULL) MyButton->Create(L"MyButton",WS_CHILD|WS_VISIBLE|SS_CENTER, CRect(120,10,220,50),this,IDC_MYBUTTON); MyEdit = new CEdit(); if (MyEdit!=NULL) MyEdit->Create(WS_CHILD|WS_VISIBLE|WS_BORDER, CRect(240,10,340,50),this,IDC_MYEDIT); } CMainWnd::~CMainWnd() { if (MyStatic!=NULL) delete MyStatic; // удалить динамический объект if (MyButton!=NULL) delete MyButton; // удалить динамический объект if (MyEdit!=NULL) delete MyEdit; // удалить динамический объект } class CMyApp : public CWinApp { public: CMyApp(); //конструктор по умолчанию virtual BOOL InitInstance();//стандартная инициализация }; CMyApp::CMyApp() // конструктор главного класса приложения {} BOOL CMyApp::InitInstance() // стандартная инициализация { m_pMainWnd=new CMainWnd(); // создать класс окна ASSERT(m_pMainWnd); // проверить его правильность m_pMainWnd->ShowWindow(SW_SHOW);// Показать окно m_pMainWnd->UpdateWindow(); // Обновить окно return TRUE; // Вернуть что все нормально }; CMyApp theApp; // запуск приложения |
так же еще бывает такая штука
1 2 3 4 5 |
BEGIN_MESSAGE_MAP(CSpaceship,CCmdTarget) //{{AFX_MSG_MAP(CSpaceship) //ПРИМЕЧАНИЕ: здесь ClassWizard будет добавлять и удалять макросы //}}AFX_MSG_MAP END_MESSAGE_MAP() |
эти комментарии указывают куда будет мастер вставлять код, когда вы будете добавлять обработчики сообщений.
BEGIN_INTERFACE_MAP, END_INTERFACE_MAP, INTERFACE_PART и BEGIN_INTERFACE_PART, END_INTERFACE_PART, STDMETHOD_, DECLARE_INTERFACE_MAP
Да как мы видим очень много макросов нужно разобрать, начнем с этих макросов которые формируют код:
1 2 3 4 |
BEGIN_INTERFACE_MAP(CSpaceship,CCmdTarget) INTERFACE_PART(CSpaceship,IID_IMotion,Motion) INTERFACE_PART(CSpaceship,IID_IVisual,Visual) END_INTERFACE_MAP() |
Описание BEGIN_INTERFACE_MAP, INTERFACE_PART и END_INTERFACE_MAP
Определение BEGIN_INTERFACE_MAP:
1 2 3 4 5 6 7 |
#define BEGIN_INTERFACE_MAP(theClass, theBase) \ const AFX_INTERFACEMAP* theClass::GetInterfaceMap() const \ { return &theClass::interfaceMap; } \ AFX_COMDAT const AFX_INTERFACEMAP theClass::interfaceMap = \ { &theBase::interfaceMap, &theClass::_interfaceEntries[0], }; \ AFX_COMDAT const AFX_INTERFACEMAP_ENTRY theClass::_interfaceEntries[] = \ { \ |
Как видим создается определение функции GetInterfaceMap() которая возвращает указатель на структуру interfaceMap типа AFX_INTERFACEMAP посмотрим его определение:
1 2 3 4 5 6 7 8 9 |
struct AFX_INTERFACEMAP { #ifdef _AFXDLL const AFX_INTERFACEMAP* (PASCAL* pfnGetBaseMap)(); // NULL is root class #else const AFX_INTERFACEMAP* pBaseMap; #endif const AFX_INTERFACEMAP_ENTRY* pEntry; // map for this class }; |
это получается структура, в которой содержится указатель на саму себя pBaseMap и какой то указатель на функцию pfnGetBaseMap, наверно эта функция возвращает этот указатель. Посмотрим определение AFX_INTERFACEMAP_ENTRY:
1 2 3 4 5 |
struct AFX_INTERFACEMAP_ENTRY { const void* piid; // the interface id (IID) (NULL for aggregate) size_t nOffset; // offset of the interface vtable from m_unknown }; |
ага это структуна, на нее есть указатель, и как видим она содержит указатель на void* piid и size_t nOffset.
Ладно в самой BEGIN_INTERFACE_MAP происходит инициализация члена interfaceMap, как мы определили это структура и ей передается указатель на первый элемент массива _iterfaceEntries[0]
1 2 |
AFX_COMDAT const AFX_INTERFACEMAP theClass::interfaceMap = \ { &theBase::interfaceMap, &theClass::_interfaceEntries[0], }; |
я от смотрю на нее и не сильно понимаю что происходит, но глядя на фигурные скобки понимаю что идет инициализация struct структуры AFX_INTERFACEMAP
Так же мы видим что идет инициализация массива _interfaceEntries в фигурных скобках
1 2 |
AFX_COMDAT const AFX_INTERFACEMAP_ENTRY theClass::_interfaceEntries[] = \ { \ |
Давайте посмотрим определение END_INTERFACE_MAP
1 2 3 |
#define END_INTERFACE_MAP() \ { NULL, (size_t)-1 } \ }; \ |
тут все проще происходит инициализация последнего элемента массива _interfaceEntries {} это показывают. Глядя на весь код нам становится ясно что макросы:
1 2 |
INTERFACE_PART(CSpaceship,IID_IMotion,Motion) INTERFACE_PART(CSpaceship,IID_IVisual,Visual) |
должны заменятся на фигурные скобки {} для инициализации массива _interfaceEntries, в фигурных скобках должно быть 2 элемента, давайте посмотрим определение INTERFACE_PART:
1 2 |
#define INTERFACE_PART(theClass, iid, localClass) \ { &iid, offsetof(theClass, m_x##localClass) }, \ |
Ну да так оно и есть, как мы вдидим возвращается два параметра в скобках, первый параметрах это const void* piid; // the interface id (IID) (NULL for aggregate), а второй это size_t nOffset; // offset of the interface vtable from m_unknown.
То есть конструкция:
1 2 3 4 |
BEGIN_INTERFACE_MAP(CSpaceship,CCmdTarget) INTERFACE_PART(CSpaceship,IID_IMotion,Motion) INTERFACE_PART(CSpaceship,IID_IVisual,Visual) END_INTERFACE_MAP() |
создаст нам определение функции GetInterfaceMap инициализирует переменную AFX_COMDAT const AFX_INTERFACEMAP theClass::interfaceMap и инициализирует массив AFX_COMDAT const AFX_INTERFACEMAP_ENTRY theClass::_interfaceEntries[], которому мы передаем значения в виде макросов INTERFACE_PART.
Переходим теперь к макросам интерфейсов которые определяются в классе это DECLARE_INTERFACE_MAP, посмотрим его определение:
1 2 3 4 5 6 |
define DECLARE_INTERFACE_MAP() \ private: \ static const AFX_INTERFACEMAP_ENTRY _interfaceEntries[]; \ protected: \ static const AFX_INTERFACEMAP interfaceMap; \ virtual const AFX_INTERFACEMAP* GetInterfaceMap() const; \ |
Что и доказывает что есть две статические переменные _interfaceEntries[] массив и interfaceMap указатель вроде как и прототип функции GetInterfacemap
Идем дальше в классе еще существую другие записи от такие:
1 2 3 4 |
BEGIN_INTERFACE_PART(Motion,IMotion) STDMETHOD_(void,Fly)(); STDMETHOD_(int&,GetPosition)(); END_INTERFACE_PART(Motion) |
Посмотрим определение BEGIN_INTERFACE_PART:
1 2 3 4 5 6 7 |
#define BEGIN_INTERFACE_PART(localClass, baseClass) \ class X##localClass : public baseClass \ { \ public: \ STDMETHOD_(ULONG, AddRef)(); \ STDMETHOD_(ULONG, Release)(); \ STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj); \ |
Здесь видно что начинается создаваться класс x##localClass : public baseClass, там вроде как добавляются 3 метода Addref, Release и QueryInterface, щас посмотрим определение макроса STDMETHOD_:
1 |
#define STDMETHOD_(type,method) virtual COM_DECLSPEC_NOTHROW type STDMETHODCALLTYPE method |
Определение STDMETHOD:
1 |
#define STDMETHOD(method) virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE method |
Определение HRESULT:
1 |
typedef __success(return >= 0) long HRESULT; |
Определение COM_DECLSPEC_NOTHROW:
1 |
#define COM_DECLSPEC_NOTHROW DECLSPEC_NOTHROW |
Определение DECLSPEC_NOTHROW:
1 |
#define DECLSPEC_NOTHROW __declspec(nothrow) |
это как мы видим директива препроцессора. Этот атрибут сообщает компилятору, что и объявленная функция, и функции, которые она вызывает, никогда не создают исключений. Почитать можно тут. Посмотрим определение STDMETHODCALLTYPE:
1 |
#define STDMETHODCALLTYPE __stdcall |
запишем полностью
1 2 3 4 5 6 7 |
#define BEGIN_INTERFACE_PART(localClass, baseClass) \ class X##localClass : public baseClass \ { \ public: \ virtual __declspec(nothrow) ULONG __stdcall AddRef(); virtual __declspec(nothrow) ULONG __stdcall Release(); virtual __declspec(nothrow) __success(return >= 0) __stdcall QueryInterface(REFIID iid, LPVOID* ppvObj); |
от такое получается создаются определения функций.
Посмотрим определение END_INTERFACE_PART:
1 2 3 |
#define END_INTERFACE_PART(localClass) \ } m_x##localClass; \ friend class X##localClass; \ |
как мы видим делает конец класса и добавляет встраиваемый член m_x##localClass и делает наш класс другом
Определение STDMETHOD_(void,Fly)(); мы уже знаем тут создастся определение функции:
1 |
virtual __declspec(nothrow) void __stdcall Fly(); |
и для второго определения STDMETHOD_(int&,GetPosition)(); функция:
1 |
virtual __declspec(nothrow) int& __stdcall GetPosition(); |
И теперь давайте все вместе запишем что у нас получилось:
1 2 3 4 5 6 7 8 9 10 |
class X##localClass : public baseClass { public: virtual __declspec(nothrow) ULONG __stdcall AddRef(); virtual __declspec(nothrow) ULONG __stdcall Release(); virtual __declspec(nothrow) __success(return >= 0) __stdcall QueryInterface(REFIID iid, LPVOID* ppvObj); virtual __declspec(nothrow) void __stdcall Fly(); virtual __declspec(nothrow) int& __stdcall GetPosition(); } m_x##localClass; friend class X##localClass; |
Вот такой от класс создается, то есть создается интерфейсный класс и определения этих классов записывается вне класса в виде:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
STDMETHODIMP_(ULONG) CSpaceship::XMotion::Release() { TRACE("CSpaceship::XMotion::Release\n"); METHOD_PROLOGUE(CSpaceship,Motion) return pThis->ExternalRelease(); } STDMETHODIMP CSpaceship::XMotion::QueryInterface(REFIID iid, LPVOID* ppvObj) { ITrace(iid, "CSpaceship::XMotion::QueryInterface"); METHOD_PROLOGUE(CSpaceship,Motion) return pThis->ExternalQueryInterface(iid,ppvObj); } |
Смотрим определение STDMETHODIMP_:
1 |
#define STDMETHODIMP_(type) type STDMETHODCALLTYPE |
Смотрим определение STDMETHODCALLTYPE:
1 |
#define STDMETHODCALLTYPE __stdcall |
Смотрим определение STDMETHODIMP:
1 |
#define STDMETHODIMP HRESULT STDMETHODCALLTYPE |
Ну в общем добавляются директивы препроцессора и всякая фигня перед функцией.
Попробуем создать реальный пример с использованием этих макросов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
#include <iostream> using std::cout; #include <afxwin.h> //идентификаторы интерфейсов //{692D03A-C689-11CE-B337-88EA36DE9E4E} static const IID IID_IOne= {0x692d03a4,0xc689,0x11ce,{0xb3,0x37,0x88,0xae,0x36,0xde,0x9e,0x4e}}; //{692D0eA5-C689-11CE-B337-88EA36DE9E4E} static const IID IID_ITwo= {0x692d03a5,0xc689,0x11ce,{0xb3,0x37,0x88,0xea,0x36,0xde,0x9e,0x4e}}; //------Интерфейсы COM----- struct IOne : public IUnknown { STDMETHOD_(void,One_Print)()=0; }; struct ITwo : public IUnknown { STDMETHOD_(void,Two_Print)()=0; }; //-----Сам объект COM----- class CMy_COM : public CCmdTarget { private: int m_pOne; int m_pTwo; protected: CMy_COM():m_pOne(10),m_pTwo(20){TRACE("CMy_COM\n");} virtual ~CMy_COM(){TRACE("~CMy_COM\n");} //для динамического создания DECLARE_DYNCREATE(CMy_COM) //создаем вложеные классы интерфейсы //XOne m_xOne BEGIN_INTERFACE_PART(One,IOne) STDMETHOD_(void,One_Print)(); END_INTERFACE_PART(One) //XTwo m_xTwo BEGIN_INTERFACE_PART(Two,ITwo) STDMETHOD_(void,Two_Print)(); END_INTERFACE_PART(Two) }; IMPLEMENT_DYNCREATE(CMy_COM,CCmdTarget) //-----------определение функций интерфейсов------- //---IOne //1.Стандарт из IUnknown STDMETHODIMP_(ULONG) CMy_COM::XOne::AddRef() { TRACE("CMy_COM::XOne::AddRef\n"); METHOD_PROLOGUE(CMy_COM,One) return pThis->ExternalAddRef(); } STDMETHODIMP_(ULONG) CMy_COM::XOne::Release() { TRACE("CMy_COM::XOne::Release\n"); METHOD_PROLOGUE(CMy_COM,One) return pThis->ExternalRelease(); } STDMETHODIMP CMy_COM::XOne::QueryInterface(REFIID iid, LPVOID* ppvObj) { TRACE("CMy_COM::XOne::QueryInterface iid=%d", iid); METHOD_PROLOGUE(CMy_COM,One) return pThis->ExternalQueryInterface(&iid,ppvObj); } //2. собственная функция STDMETHODIMP_(void) CMy_COM::XOne::One_Print() { TRACE("CSpaceship::XMotion::Fly\n"); METHOD_PROLOGUE(CMy_COM,One) TRACE("m_pOne=%d\n",pThis->m_pOne); return; } //-----ITwo----- //1.Стандарт из IUnknown STDMETHODIMP_(ULONG) CMy_COM::XTwo::AddRef() { TRACE("CMy_COM::XOne::AddRef\n"); METHOD_PROLOGUE(CMy_COM,Two) return pThis->ExternalAddRef(); } STDMETHODIMP_(ULONG) CMy_COM::XTwo::Release() { TRACE("CMy_COM::XOne::Release\n"); METHOD_PROLOGUE(CMy_COM,Two) return pThis->ExternalRelease(); } STDMETHODIMP CMy_COM::XTwo::QueryInterface(REFIID iid, LPVOID* ppvObj) { TRACE("CMy_COM::XOne::QueryInterface iid=%d", iid); METHOD_PROLOGUE(CMy_COM,Two) return pThis->ExternalQueryInterface(&iid,ppvObj); } //2. собственная функция STDMETHODIMP_(void) CMy_COM::XTwo::Two_Print() { TRACE("CMy_COM::XTwo::Two_Print\n"); METHOD_PROLOGUE(CMy_COM,Two) TRACE("m_pTwo=%d\n",pThis->m_pTwo); return; } int main() { cout <<"COM-klient\r\n"; IUnknown* pIUnk=nullptr; IOne* pIOne=nullptr; ITwo* pITwo=nullptr; CMy_COM* p = (CMy_COM*)CMy_COM::CreateObject(); pIOne=&p->m_xOne; pITwo=&p->m_xTwo; pIOne->One_Print(); pITwo->Two_Print(); exit(1); //передаем идентификатор интерфейса и получаем интерфейс pIUnk->QueryInterface(IID_IOne,(void**)&pITwo); if(pITwo==nullptr){cout <<"ne naideno\r\n";} else { pITwo->QueryInterface(IID_ITwo,(void**)&pIOne); pITwo->Two_Print(); } if(pIOne==nullptr){cout <<"ne naideno\r\n";} else{pIOne->One_Print();} if(pIUnk!=nullptr)pIUnk->Release(); if(pIOne!=nullptr)pIOne->Release(); if(pITwo!=nullptr)pITwo->Release(); cout <<"end COM-klient\r\n"; return 0; } |
в этом коде я не могу еще реально использовать функции AddReff, Release и QueryInterface. Они не работают.
DECLARE_OLECREATE, IMPLEMENT_OLECREATE и IMPLEMENT_OLECREATE_FLAGS
Посмотрим определение DECLARE_OLECREATE:
1 2 3 4 |
#define DECLARE_OLECREATE(class_name) \ public: \ static COleObjectFactory factory; \ static const GUID guid; \ |
Как видим создается объявление двух статических переменных static COleObjectFactory factory и static const GUID guid, GUID — это уникальный глобальный идентификатор.
Посмотрим определение IMPLEMENT_OLECREATE:
1 2 3 4 5 |
#define IMPLEMENT_OLECREATE(class_name, external_name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ COleObjectFactory class_name::factory(class_name::guid, \ RUNTIME_CLASS(class_name), FALSE, _T(external_name)); \ AFX_COMDAT const GUID class_name::guid = \ { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \ |
тут видно идет инициализация статической переменной (объекта) COleObjectFactory class_name::factory(); и ей передаются какие то параметры и идет инициализация второго статического члена __declspec(selectany) const GUID class_name::guid={}, как видно из инициализации это структура GUID — уникальный глобальный идентификатор и определение AFX_COMDAT:
1 |
#define AFX_COMDAT __declspec(selectany) |
Посмотрим определение макроса IMPLEMENT_OLECREATE_FLAGS:
1 2 3 4 5 |
#define IMPLEMENT_OLECREATE_FLAGS(class_name, external_name, nFlags, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ COleObjectFactory class_name::factory(class_name::guid, \ RUNTIME_CLASS(class_name), FALSE, nFlags, _T(external_name)); \ AFX_COMDAT const GUID class_name::guid = \ { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; \ |
как видно происходит то же самое что и в макросе IMPLEMENT_OLECREATE, только еще добавляется флаг. Например в таком вызове:
1 2 |
// {1CCB6EAC-3853-4354-8CB2-E454C5B3AE15} IMPLEMENT_OLECREATE_FLAGS(CMyCom, "My_new_COM.MyCom", afxRegApartmentThreading, 0x1ccb6eac, 0x3853, 0x4354, 0x8c, 0xb2, 0xe4, 0x54, 0xc5, 0xb3, 0xae, 0x15) |
флаг у нас переменная afxRegApartmentThreading, ее определение:
1 2 3 4 5 6 7 |
enum AFX_REG_FLAGS { afxRegDefault = 0x0000, afxRegInsertable = 0x0001, afxRegApartmentThreading = 0x0002, afxRegFreeThreading = 0x0004, }; |
как видно IMPLEMENT_OLECREATE отличается от IMPLEMENT_OLECREATE_FLAGS отличается тем что происходит инициализация с флагом и без флага, в классе COleObjectFactory определено два конструктора, вот их определения:
1 2 3 4 5 6 |
// Construction public: COleObjectFactory(REFCLSID clsid, CRuntimeClass* pRuntimeClass, BOOL bMultiInstance, LPCTSTR lpszProgID); COleObjectFactory(REFCLSID clsid, CRuntimeClass* pRuntimeClass, BOOL bMultiInstance, int nFlags, LPCTSTR lpszProgID); |
В одном с флагом, в другом без.
Приведем примерчик использования этих макросов.
вот заголовочный файл с классом, файл CMyCom.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#pragma once // конечный объект команды CMyCom class CMyCom : public CCmdTarget { DECLARE_DYNCREATE(CMyCom) public: CMyCom(); virtual ~CMyCom(); virtual void OnFinalRelease(); protected: //создает карту сообщений DECLARE_MESSAGE_MAP() // DECLARE_OLECREATE(CMyCom) // DECLARE_DISPATCH_MAP() DECLARE_INTERFACE_MAP() }; |
и вот файл с определением функция класса, там в самом низу есть макрос IMPLEMENT_OLECREATE_FLAGS
Файл MyCom.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
// MyCom.cpp: файл реализации // #include "stdafx.h" #include "My_new_COM.h" #include "MyCom.h" // CMyCom IMPLEMENT_DYNCREATE(CMyCom, CCmdTarget) CMyCom::CMyCom() { EnableAutomation(); // Чтобы обеспечить работу приложения в течение всего периода активности объекта автоматизации OLE, // конструктор вызывает AfxOleLockApp. AfxOleLockApp(); } CMyCom::~CMyCom() { // Чтобы прервать работу приложения, когда все объекты созданы // при помощи OLE-автоматизации, деструктор вызывает AfxOleUnlockApp. AfxOleUnlockApp(); } void CMyCom::OnFinalRelease() { // Когда будет освобождена последняя ссылка на объект автоматизации, // вызывается OnFinalRelease. Базовый класс автоматически // удалит объект. Перед вызовом базового класса добавьте // дополнительную очистку, необходимую вашему объекту. CCmdTarget::OnFinalRelease(); } BEGIN_MESSAGE_MAP(CMyCom, CCmdTarget) END_MESSAGE_MAP() // Примечание: мы добавили поддержку для IID_IMyCom, чтобы обеспечить безопасную с точки зрения типов привязку // из VBA. Этот IID должен соответствовать GUID, связанному с // disp-интерфейсом в файле .IDL. // {04CB6FA4-A072-4AC8-BD06-AB04A7B7BD2B} static const IID IID_IMyCom = { 0x4CB6FA4, 0xA072, 0x4AC8, { 0xBD, 0x6, 0xAB, 0x4, 0xA7, 0xB7, 0xBD, 0x2B } }; BEGIN_INTERFACE_MAP(CMyCom, CCmdTarget) INTERFACE_PART(CMyCom, IID_IMyCom, Dispatch) END_INTERFACE_MAP() // {1CCB6EAC-3853-4354-8CB2-E454C5B3AE15} IMPLEMENT_OLECREATE_FLAGS(CMyCom, "My_new_COM.MyCom", afxRegApartmentThreading, 0x1ccb6eac, 0x3853, 0x4354, 0x8c, 0xb2, 0xe4, 0x54, 0xc5, 0xb3, 0xae, 0x15) // обработчики сообщений CMyCom |
FAILED и SUCCEEDED
Определение этих макросов:
1 2 |
#define SUCCEEDED(hr) (((HRESULT)(hr)) >= 0) #define FAILED(hr) (((HRESULT)(hr)) < 0) |
как мы видим они просто принимают аргумент типа HRESULT и смотрят больше >=0 он или <0, то есть булево значение получается, а используется он вот в этом коде:
1 2 3 4 5 |
CLSID clsid;//GUID hr = ::CLSIDFromProgID(L"My_COM", &clsid); //теперь мы просим систему найти для нас clsid по заданному ProgID if (FAILED(hr)) { //если функция отработала с ошибкой делаем что то return; } |
ProgID — это идентификатор программы, я его назвал как «My_COM» и через него функция CLSIDFromProgID возвращает GUID и мы проверяем с помощью макроса FAILED что результат у нас получилось, если результат меньше 0 то вернется true и мы выйдем из функции, если больше то будем что то делать дальше.
И можно еще так его использовать:
1 2 3 4 5 6 |
CLSID clsid;//GUID hr = ::CLSIDFromProgID(L"My_COM", &clsid); //теперь мы просим систему найти для нас clsid по заданному ProgID if (!SUCCEEDED(hr)) { //если функция отработала с ошибкой делаем что то TRACE("unable to find Program ID -- error = %x\n", hr); return; } |
DECLARE_DISPATCH_MAP() и BEGIN_DISPATCH_MAP()
END_DISPATCH_MAP()
и так посмотрим определение макроса DECLARE_DISPATCH_MAP
1 2 3 4 5 6 7 8 |
#define DECLARE_DISPATCH_MAP() \ private: \ static const AFX_DISPMAP_ENTRY _dispatchEntries[]; \ static UINT _dispatchEntryCount; \ static DWORD _dwStockPropMask; \ protected: \ static const AFX_DISPMAP dispatchMap; \ virtual const AFX_DISPMAP* GetDispatchMap() const; \ |
как видим он добавляет 3 закрытых члена, защищенный статический член и функцию которая судя из названия возвращает карту диспетчеризации. Посмотрим структуру AFX_DISPMAP ее определение
1 2 3 4 5 6 7 8 9 10 11 |
struct AFX_DISPMAP { #ifdef _AFXDLL const AFX_DISPMAP* (PASCAL* pfnGetBaseMap)(); #else const AFX_DISPMAP* pBaseMap; #endif const AFX_DISPMAP_ENTRY* lpEntries; UINT* lpEntryCount; DWORD* lpStockPropMask; }; |
и глянем определение AFX_DISPMAP_ENTRY
1 2 3 4 5 6 7 8 9 10 11 |
struct AFX_DISPMAP_ENTRY { LPCTSTR lpszName; // member/property name long lDispID; // DISPID (may be DISPID_UNKNOWN) LPCSTR lpszParams; // member parameter description WORD vt; // return value type / or type of property AFX_PMSG pfn; // normal member On<membercall> or, OnGet<property> AFX_PMSG pfnSet; // special member for OnSet<property> size_t nPropOffset; // property offset AFX_DISPMAP_FLAGS flags;// flags (e.g. stock/custom) }; |
В общем добавляются к классу данные 4 члена и функция.
Так смотрим макросы которые создают карту диспетчеризации, я подозреваю они инициализируют все эти 4 переменных, глянем определение макроса BEGIN_DISPATCH_MAP
1 2 3 4 5 6 7 8 9 10 |
#define BEGIN_DISPATCH_MAP(theClass, baseClass) \ const AFX_DISPMAP* theClass::GetDispatchMap() const \ { return &theClass::dispatchMap; } \ AFX_COMDAT const AFX_DISPMAP theClass::dispatchMap = \ { &baseClass::dispatchMap, &theClass::_dispatchEntries[0], \ &theClass::_dispatchEntryCount, &theClass::_dwStockPropMask }; \ AFX_COMDAT UINT theClass::_dispatchEntryCount = (UINT)-1; \ AFX_COMDAT DWORD theClass::_dwStockPropMask = (DWORD)-1; \ AFX_COMDAT const AFX_DISPMAP_ENTRY theClass::_dispatchEntries[] = \ { \ |
видно создается определение добавленной функции GetDispatchMap() и дальше идет инициализация добавленных ранее членов. Так же видно что заполняется массив dispatchEntries[].
Посмотрим определение END_DISPATCH_MAP
1 2 3 |
#define END_DISPATCH_MAP() \ { VTS_NONE, DISPID_UNKNOWN, VTS_NONE, VT_VOID, \ (AFX_PMSG)NULL, (AFX_PMSG)NULL, (size_t)-1, afxDispCustom } }; \ |
Видно что добавился последний элемент к массиву и все конец.
Что то я не увидел в этом классе IDispatch.