MESAJ HARİTALARI
Bir mesaj oluştuğunda bir sınıfın bir üye fonksiyonunun çağrılması iki biçimde gerçekleşebilir:
1) Sanal fonksiyon mekanizmasıyla. Örneğin WM_COMMAND mesajı için CWnd::OnCommand sanal fonksiyonunun çağrılmasında olduğu gibi.

2) Mesaj haritaları yoluyla.
MFC sisteminde sanal fonksiyon yöntemi yoğun olarak kullanılmamıştır. Sanal fonksiyonlar yerine mesaj haritaları daha baskın olarak tercih edilmiştir.
2001-10-25 Perşembe
Mesaj haritası sınıfın statik bir yapı dizisidir. Bu yapı dizisinde kabaca hangi mesajlar için hangi üye fonksiyonların çağrılacağı bilgileri vardır. Mesaj haritası WindowProc sanal fonksiyonunun çağırdığı OnWndMsg fonksiyonu tarafından aranır. Yani OnWndMsg mesajı WM_COMMAND ya da WM_NOTIFY değilse sınıfın mesaj haritasına bakmaktadır. Mesaj haritasının kurulabilmesi için sınıf bildiriminde ve tanımlamasında çeşitli eklentilerin yapılması gerekir. Mesaj haritası kurmak için gereken düzenlemeler birkaç makro tarafından kolaylıkla yapılabilir. Mesaj haritasını oluşturan AFX_MSGMAP_ENTRY yapısı şöyledir:

struct AFX_MSGMAP_ENTRY {
UINT nMessage;// mesajın numarasını içerir
UINT nCode; //
UINT nID; //
UINT nLastID; //
UINT nSig; // çağrılacak üye fonksiyonun parametrik yapısını belirler
AFX_PMSG pfn; // çağrılacak üye fonksiyonun adresini tutmaktadır
};

Yapının nMessage elemanı mesaj oluştuğunda mesajın numarasını içerir. nSig elemanı çağrılacak üye fonksiyonun parametrik yapısını belirlemek için kullanılır. pfn elemanı ise mesaj oluştuğunda çağrılacak üye fonksiyonun adresini tutmaktadır. pfn üye fonksiyon göstericisi genel bir göstericidir. Bu üye fonksiyon çağrılacağı zaman orijinal biçimine dönüştürülecektir. Üye fonksiyon göstericisi AFX_PMSG tür ismiyle temsil edilmiştir. Bu tür aşağıdaki gibi typedef edilmiştir:

typedef void (CCmdTarget::*AFX_PMSG)(void);

Bir sınıfa mesaj haritası yerleştirebilmek için ilk yapılacak işlem DECLARE_MESSAGE_MAP( ) isimli makronun sınıf bildirimi içerisine yerleştirilmesidir. Bu makro içerisinde bölüm belirleme işlemi yapıldığından makronun sınıf bildiriminin sonuna yerleştirilmesi daha uygundur. Örneğin:

class CMainWnd : public CFrameWnd {
// …..
DECLARE_MESSAGE_MAP()
};

DECLARE_MESSAGE_MAP( ) makrosu şöyle yazılmıştır:

#define DECLARE_MESSAGE_MAP() \
private: \
static const AFX_MSGMAP_ENTRY _messageEntries[ ]; \
protected: \
static AFX_DATA const AFX_MSGMAP messageMap; \
virtual const AFX_MSGMAP *GetMessageMap() const;

Burada messageEntries isimli dizi söz konusu mesaj haritasıdır. Tabii CWnd::OnWndMsg adresiyle belirlediği nesnenin hangi sınıfa ilişkin olduğunu bilemez. Bilemediği için de bu mesaj dizisini tespit edemez. Bu nedenle makro sınıfa bu dizinin başlangıç adresini veren GetMessageMap isimli bir üye fonksiyon da eklemiştir. GetMessageMap sanal fonksiyonu aslında doğrudan messageEntries dizisinin başlangıç adresiyle geri dönmez. Sınıfa eklenen messageMap yapısının adresiyle geri döner. Bu yapının içerisinde dizinin başlangıç adresi tutulmaktadır. Burada söz konusu olan AFX_MSGMAP yapısı şöyledir:

struct AFX_MSGMAP {
const AFX_MSGMAP *pBaseMap;
const AFX_MSGMAP_ENTRY *lpEntries;
};

Bu yapının içerisinde hem mesaj haritasına ilişkin dizinin başlangıç adresi hem de taban sınıfın mesaj haritasının başlangıç adresi bulunmaktadır.
Özetle DECLARE_MESSAGE_MAP makrosu ile şunlar yapılmaktadır:
1) Mesaj haritasına ilişkin AFX_MSGMAP_ENTRY türünden statik bir dizinin bildirimi sınıfa eklenir.
2) Bu statik dizinin başlangıç adresinin elde edilmesi için GetMessageMap isimli sanal bir fonksiyonun prototipi sınıfa eklenir.
3) GetMessageMap sanal fonksiyonu doğrudan statik dizinin başlangıç adresiyle geri dönmez. Sınıfa yerleştirilen statik bir yapının başlangıç adresiyle geri döner. Dizinin adresi bu yapıda yazmaktadır. Yani dizinin adresine dolaylı bir biçimde erişilmektedir.
Sonuç olarak türetme ağacının herhangi bir yerinde sınıfa yerleştirilen GetMessageMap sanal fonksiyonunu çağırarak mesaj haritasına ilişkin diziyi inceleyebiliriz. Zaten OnWndMsg fonksiyonu da mesaj hartasını böyle ele geçirmektedir.
DECLARE_MESSAGE_MAP makrosu ile yapılan bildirimlerin tanımlamaları BEGIN_MESSAGE_MAP ve END_MESSAGE_MAP makrolarıyla yapılır. Bir sınıf .H ve .CPP dosyalarıyla yazıldığına göre bu makrolar .CPP dosyasının tepesine global düzeyde yerleştirilmelidir. BEGIN_MESSAGE_MAP makrosu iki parametreli bir makrodur. Birinci parametresi mesaj haritasının yerleştirildiği sınıfın ismi, ikinci parametresi ise bu sınıfın taban sınıf ismi olmak zorundadır. END_MESSAGE_MAP makrosunun parametresi yoktur. Bu durumda bu makrolar tipik olarak aşağıdaki gibi oluşturulabilir:

BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)
END_MESSAGE_MAP()

BEGIN_MESSAGE_MAP makrosu şöyle yazılmıştır:

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
const AFX_MSGMAP *theClass::GetMessageMap() const; \
{ return &theClass::messageMap; } \
AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[ ] = \
{ \

END_MESSAGE_MAP makrosu da şöyle yazılmıştır:

#define END_MESSAGE_MAP() \
{0, 0, 0, 0 AfxSig_end, (AFX_PMSG)0} \
}; \

BEGIN_MESSAGE_MAP makrosunda şunlar yapılmıştır:
1) Bildirimde belirtilen GetMessageMap sanal fonksiyonu yazılmıştır.
2) Mesaj harita dizisinin başlangıç adresi bildirimde belirtilen messageMap isimli statik veri elemanına yazılmıştır.
3) Mesaj harita dizisi global düzeyde ilk değer veriliyormuş gibi tanımlanmıştır. Makronun yerleştirdiği son şey bu mesaj harita dizisine ilk değer vermeyi başlatan küme parantezidir. Yani BEGIN_MESSAGE_MAP makrosundan sonra yazılan ifadeler bu diziye verilen ilk değerler gibi ele alınacaktır.
END_MESSAGE_MAP mesaj harita dizisinin sonunun tespit edilebilmesi için bir eleman yerleştirerek ilk değer verme küme parantezini kapatır.