ما هو MFC في Windows؟ ── المعرفة الأساسية لصيانة الأصول القائمة
· آخر تحديث: · غو كومورا · Windows, MFC, VisualC++, Cpp, Win32, NativeApp, DesktopApp, LegacyCode, إعادة استخدام الأصول القائمة
1. ما يجب معرفته أولاً
عند صيانة تطبيقات سطح مكتب Windows القديمة، قد تصادف أسماء كهذه.
CWinApp
CWnd
CDialog
CDialogEx
CFrameWnd
CDocument
CView
CString
CFile
CArchive
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_BN_CLICKED
DoDataExchange
UpdateData
هذه أسماء تظهر كثيراً في إطار عمل تطبيقات Windows الموجَّه إلى C++ يُدعى MFC. واختصار MFC هو Microsoft Foundation Classes، وهو مكتبة تجعل التعامل مع Win32 API أسهل من خلال تمثيله كفئات C++.
في تطوير تطبيقات Windows اليوم، تتوفّر خيارات متعدّدة مثل WinUI وWPF وWindows Forms وElectron وQt وتقنيّات الويب، لذا قلّت الحالات التي يُعامَل فيها MFC كخيار أوّل للتطوير الجديد. ومع ذلك، فهو ليس تقنيّة اندثرت. لا تزال تطبيقات الأعمال، وأجهزة القياس، وبرمجيّات التحكّم، وأنظمة CAD/CAM، والأدوات الداخليّة، وبرمجيّات الحزم القديمة، تحتفظ بقواعد شيفرة MFC التي تحتاج إلى صيانة حتّى الآن.
نذكر أوّلاً وجهة النظر التي ينبغي التحلّي بها لفهم MFC.
MFC خريطة أساسية لقراءة تطبيقات سطح مكتب Windows القديمة
تعامل مع MFC لا كإخفاء لـ Win32 API، بل كغلاف له بأسلوب C++
دون معرفة أسلوب MFC، ستخطئ في قراءة السلوك أكثر ممّا يوحي به شكل الشيفرة
تكمن قيمته في صيانة الأصول القائمة وإطالة عمرها والانتقال التدريجيّ منها، لا في اعتماده الجديد
في هذا المقال، سنستعرض لمحة عامّة عن MFC، وبنية التطبيق، وخريطة الرسائل (Message Map)، وDocument/View، ومربّعات الحوار، وDDX/DDV، والموارد (Resources)، والبناء (Build)، ونقاط الحذر عند الصيانة.
كما أنّ مقاطع الشيفرة الواردة في هذا المقال منشورة على GitHub كمجموعة شيفرات مرجعيّة مرتّبة حسب الفصول.
windows-mfc-overview - komurasoft-blog-samples (GitHub)
2. ما هو MFC
MFC مكتبة فئات (Class Library) لبناء تطبيقات سطح مكتب Windows الأصليّة (Native) بلغة C++.
عند استخدام Win32 API مباشرةً، عادةً ما تُكتَب شيفرة من هذا النوع.
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
// معالجة الرسم
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
يُعدّ Win32 API قويّاً جدّاً، لكنّه يُبنى أساساً حول دوالّ C والمقابض (Handles) والرسائل (Messages) واستدعاءات الرجوع (Callbacks)، ما يجعل الرؤية الشاملة تضعف بسهولة في التطبيقات الكبيرة. وMFC هو ما يجعل التعامل مع هذا ممكناً كفئات C++، حيث تُمثَّل النافذة بالفئة CWnd، ومربّع الحوار بالفئة CDialog، والتطبيق ككلّ بالفئة CWinApp، ونافذة الإطار (Frame Window) بالفئة CFrameWnd، والعرض (View) بالفئة CView، وما شابه.
class CMainFrame : public CFrameWnd
{
public:
CMainFrame();
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
MFC ليس استبدالاً كاملاً لـ Win32 API بشيء آخر، بل هو إطار عمل C++ رقيق لكن واسع النطاق، مبنيّ على أساس فكر Win32 API. لذا فقراءة MFC تتطلّب، إلى جانب فئات MFC نفسها، معرفة هذه الأمور أيضاً.
رسائل Windows
المقابض (Handles) مثل HWND
GDI/GDI+
ملفّات الموارد
COM/OLE
مكتبات DLL وبيئات التشغيل (Runtime)
ترميز الأحرف
مؤشّرات الترابط وحلقة الرسائل
بدلاً من وصفه بـ«مكتبة سحريّة يمكن الكتابة بها دون معرفة Windows»، فإنّ الحقيقة الفعليّة لـ MFC هي «تنظيم لآليّات Windows ضمن أنواع C++ وإطار عمل».
3. هل لا يزال MFC قابلاً للاستخدام اليوم
لا يزال MFC متاحاً حتّى الآن في Visual Studio. لكن يجب الحذر من سوء فهم وضعه. فرغم استمرار دعمه، إلا أنّه ليس إطار واجهة مستخدم حديثاً تُضاف إليه ميزات جديدة بنشاط، وتشير وثائق MFC من Microsoft إلى أنّه سيستمرّ دعمه لكن لن تُضاف ميزات جديدة ولن تُحدَّث الوثائق.
لذا فوضع MFC يصبح تقريباً كالتالي.
صيانة تطبيقات MFC القائمة -> شائع عمليّاً
إضافة ميزات إلى تطبيقات MFC القائمة -> ممكن
تحديث بيئة بناء تطبيقات MFC -> مهمّ
الانتقال التدريجيّ من MFC إلى واجهة أخرى -> ممكن
اعتماده في تطبيق واجهة رسوميّة عامّ جديد كليّاً -> يتطلّب حذراً
خصوصاً في تطبيقات الأعمال المستخدَمة منذ زمن طويل، قد تكون واجهة المستخدم والطباعة وإدخال/إخراج الملفّات والتحكّم بالأجهزة والبروتوكولات الخاصّة والتكامل مع COM مجتمعة داخل MFC.
في قواعد شيفرة كهذه، فإنّ «جعل MFC قابلاً للقراءة» مطلوب قبل «التخلّص من MFC».
4. المجالات التي كان MFC بارعاً فيها
المجال التمثيليّ الذي استُخدم فيه MFC هو تطبيقات سطح مكتب Windows الأصليّة.
وبتحديد أكبر، هي تطبيقات من هذا النوع.
أدوات الأعمال التي محورها مربّع الحوار
تطبيقات SDI التي تفتح الملفّات وتحرّرها
تطبيقات MDI التي تتعامل مع مستندات متعدّدة
شاشات التحكّم بأجهزة القياس وأجهزة التصنيع
تطبيقات أصليّة من نوع CAD/CAM
تطبيقات تستخدم الطباعة والمعاينة بكثرة
تطبيقات تتضمّن تكامل ActiveX أو OLE
تطبيقات وثيقة الصلة بواجهات Windows API القديمة وأصول COM
تكمن قوّة MFC في عمله بالقرب من مكوّنات Windows الأصليّة. فالنوافذ والقوائم وأشرطة الأدوات وأشرطة الحالة ومربّعات الحوار وعناصر التحكّم الشائعة (Common Controls) والطباعة ومربّعات حوار الملفّات وسجلّ النظام (Registry) ورسم GDI، يمكن التعامل معها كفئات C++.
في المقابل، يكمن ضعف MFC في أنّ بناء واجهة المستخدم الحديثة، وربط البيانات، وسهولة الاختبار، والمعالجة غير المتزامنة (Asynchronous)، والتخطيطات (Layouts) الحديثة، ودعم DPI العالي، والتدويل، وإمكانيّة الوصول (Accessibility)، لا يمكن كتابتها بسهولة كما في الأطر الأحدث.
عند سرد الخصائص، نصل إلى هذا.
قريب من مكوّنات Windows الأصليّة
يمكن التحكّم به مباشرةً بلغة C++
يمتلك أصولاً قائمة كثيرة
يتطلّب معرفة بـ Win32
يحتوي على أساليب قديمة كثيرة
يلزم تنظيم البنية بنفسك لتصبح قابلة للاختبار
5. التحضير لاستخدام MFC في Visual Studio
حتّى لو ثبَّتَّ C++ في Visual Studio، فهذا لا يعني بالضرورة أنّ MFC مثبَّت. فـMFC يُعامَل كمكوّن فرديّ (Individual Component) في Visual Studio Installer. من الأمثلة التمثيليّة، تحقّق من هذه المكوّنات.
تطوير سطح المكتب بلغة C++
MSVC v143 - VS 2022 C++ x64/x86 build tools
Windows SDK
C++ MFC for latest v143 build tools
C++ ATL for latest v143 build tools
ما إذا كانت هناك حاجة لإصدار MFC من نوع Spectre Mitigations
عند عدم العثور على ملفّات متعلّقة بـ MFC أثناء البناء، تحقّق ليس فقط من إعدادات المشروع، بل أيضاً من وجود مكوّنات MFC في Visual Studio نفسه.
الأمر نفسه في بيئات CI وخوادم البناء؛ إن كان البناء ينجح على Visual Studio المحليّ لكنّه يفشل على CI، فقد يكون السبب وجود مكوّنات MFC من عدمه، أو اختلاف إصدار مجموعة الأدوات (Toolset) المستهدَفة.
6. البنية الأساسيّة لتطبيق MFC
يمتلك تطبيق MFC عادةً بنية كالتالي.
فئة مشتقّة من CWinApp
تتولّى تهيئة التطبيق ككلّ وإنهاءه
فئات مشتقّة من CFrameWnd / CMDIFrameWnd / CDialog
تتولّى النافذة الرئيسيّة أو مربّعات الحوار
فئة مشتقّة من CView
تتولّى عرض الشاشة وتفاعل المستخدم
فئة مشتقّة من CDocument
تتولّى البيانات وحفظ الملفّات
ملفّ الموارد
يحمل القوائم ومربّعات الحوار والأيقونات والسلاسل النصّيّة وغيرها
خريطة الرسائل
تربط رسائل Windows والأوامر بدوالّ المعالجة
مثلاً، في تطبيق MFC بسيط، تظهر فئة مشتقّة من CWinApp كهذه.
class CMyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
CMyApp theApp;
BOOL CMyApp::InitInstance()
{
CWinApp::InitInstance();
CMainFrame* pFrame = new CMainFrame;
m_pMainWnd = pFrame;
pFrame->Create(nullptr, _T("My MFC Application"));
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
CWinApp فئة تمثّل التطبيق ككلّ.
في تطبيق MFC، يوجد عادةً كائن واحد مشتقّ من CWinApp.
قد يبدو هذا الكائن العامّ (Global Object) مثل theApp غريباً للوهلة الأولى، لكنّه بنية قياسيّة في MFC.
7. ماذا يفعل CWinApp
CWinApp مهمّ كمدخل لتطبيق MFC. ففي تطبيق Win32 العاديّ، تكتب بنفسك WinMain وتسجيل فئة النافذة (Window Class) وحلقة الرسائل وغيرها، لكن في MFC يتولّى إطار العمل معظم ذلك. يقوم المطوّر بشكل أساسيّ بتجاوز (Override) الدالّة InitInstance لكتابة التهيئة الخاصّة بالتطبيق.
BOOL CMyApp::InitInstance()
{
CWinApp::InitInstance();
// تحميل الإعدادات
// تهيئة COM
// إنشاء النافذة الرئيسيّة
// تسجيل قالب المستند
return TRUE;
}
من المعالجات الشائعة كتابتها في InitInstance:
تهيئة عناصر التحكّم الشائعة (Common Controls)
إعداد مفتاح سجلّ النظام (Registry Key)
تحميل قائمة الملفّات المستخدَمة مؤخّراً
تسجيل قوالب المستندات (Document Templates)
إنشاء الإطار الرئيسيّ (Main Frame)
معالجة وسيطات سطر الأوامر
تهيئة COM/OLE
عند الصيانة، يساعد التحقّق أوّلاً من الفئة المشتقّة من CWinApp في رؤية ترتيب بدء تشغيل التطبيق ككلّ بوضوح أكبر.
8. CWnd فئة في صميم MFC
معظم فئات واجهة المستخدم في MFC تعتمد على CWnd التي تمثّل نافذة Windows كفئة أساس (Base Class). لكن كائن CWnd وHWND ليسا الشيء نفسه.
HWND
مقبض نافذة (Window Handle) يديره نظام Windows
CWnd
كائن غلاف (Wrapper) بلغة C++ يجعل التعامل مع HWND أسهل
في MFC، يحمل CWnd بداخله HWND.
HWND hWnd = m_hWnd;
أو يُحصَل عليه كما يلي.
HWND hWnd = GetSafeHwnd();
المهمّ عند الصيانة هو أنّه حتّى لو كان CWnd* موجوداً، فقد يكون HWND المقابل له قد أُتلِف (Destroyed) بالفعل.
لذا، يُتحقَّق من صلاحيّة النافذة بهذا الشكل.
if (pWnd != nullptr && ::IsWindow(pWnd->GetSafeHwnd()))
{
pWnd->ShowWindow(SW_SHOW);
}
عند التحقيق في أخطاء MFC، من المهمّ ملاحظة ما إذا كان عمر كائن CWnd بلغة C++ وعمر مقبض نافذة Windows الفعليّ غير متطابقَين.
9. ما هي خريطة الرسائل (Message Map)
أحد الآليّات التي يظهر فيها طابع MFC بأوضح صورة هي خريطة الرسائل (Message Map).
تستقبل تطبيقات Windows نقرات الفأرة، وإدخال لوحة المفاتيح، وإعادة الرسم، وتغيير حجم النافذة، واختيار القوائم، وغيرها، كرسائل Windows.
في Win32 API، عادةً ما تُعالَج الرسائل عبر جملة switch في WndProc.
أمّا في MFC، فيُربَط ذلك بدوالّ المعالجة عبر خريطة الرسائل.
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_BN_CLICKED(IDC_BUTTON_OK, &CMyDialog::OnClickedButtonOk)
ON_WM_CLOSE()
END_MESSAGE_MAP()
void CMyDialog::OnClickedButtonOk()
{
AfxMessageBox(_T("Clicked"));
}
معنى هذه الشيفرة هو التالي.
عند النقر على الزرّ المسمّى IDC_BUTTON_OK
استدعِ CMyDialog::OnClickedButtonOk
من دون اعتياد قراءة MFC، يصعب معرفة مصدر استدعاء الدالّة. إن لم تجد استدعاءً مباشراً رغم البحث، فانظر إلى خريطة الرسائل.
الدالّة لا تُستدعى مباشرةً
لكنّها تُنفَّذ عند وقوع الحدث
-> تحقّق من وحدات ماكرو BEGIN_MESSAGE_MAP / ON_...
في مراجعة شيفرة MFC، من المهمّ عدم الاكتفاء بالنظر إلى دوالّ المعالجة وحدها، بل التحقّق منها مع خريطة الرسائل معاً.
10. توجيه الأوامر (Command Routing)
في MFC، تُعامَل عمليّات القوائم وأشرطة الأدوات أيضاً كأوامر (Commands).
الأكثر تمثيلاً هو ON_COMMAND.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)
END_MESSAGE_MAP()
void CMainFrame::OnFileOpen()
{
// معالجة فتح الملفّ
}
يمتلك MFC آليّة لتوجيه الأوامر إلى الكائن المناسب.
مثلاً، حتّى مع نفس ID_EDIT_COPY، قد يختلف من يعالجه بين العرض النشط حاليّاً، والمستند، والإطار، والتطبيق.
العرض النشط (Active View)
المستند (Document)
نافذة الإطار (Frame Window)
التطبيق (Application)
بهذا الترتيب، يمرّ الأمر إلى الكائن القادر على معالجته.
لذا، قد يصعب في MFC تتبّع «أيّ دالّة تُستدعى عند الضغط على القائمة» عبر البحث النصّيّ البسيط وحده.
النقاط التي يُنظَر إليها عند الصيانة هي هذه.
ما هو معرّف الأمر (Command ID)
في أيّ فئة يوجد ON_COMMAND
أين يوجد ON_UPDATE_COMMAND_UI
ما هو العرض النشط حاليّاً
هل يُستخدَم بنية Document/View
11. ما هو ON_UPDATE_COMMAND_UI
في MFC، يُستخدَم أحياناً ON_UPDATE_COMMAND_UI لتحديث تفعيل/تعطيل عناصر القوائم أو أزرار شريط الأدوات، وحالة التحديد (Check)، ونصّ العرض، وغيرها.
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_COMMAND(ID_EDIT_DELETE, &CMainFrame::OnEditDelete)
ON_UPDATE_COMMAND_UI(ID_EDIT_DELETE, &CMainFrame::OnUpdateEditDelete)
END_MESSAGE_MAP()
void CMainFrame::OnUpdateEditDelete(CCmdUI* pCmdUI)
{
pCmdUI->Enable(CanDeleteCurrentItem());
}
بهذا يمكن تفعيل القائمة أو الزرّ فقط في حالة إمكانيّة الحذف.
عند استخدام تطبيق MFC، إذا أردتَ التحقيق في سلوك مثل ظهور زرّ رماديّ (Grayed-out) دون سبب واضح، أو تعذّر الضغط على قائمة، أو تغيّر حالة التحديد، فإنّ البحث عن ON_UPDATE_COMMAND_UI قد يكشف السبب.
12. تطبيقات MFC القائمة على مربّع حوار
أحد أوضح أشكال MFC هو التطبيق القائم على مربّع حوار (Dialog-based Application).
في شاشات الإعدادات وأدوات الأعمال البسيطة وشاشات تشغيل الأجهزة، قد تكون البنية محورها مربّع الحوار.
عادةً، تُشتقّ من CDialog أو CDialogEx.
class CSettingsDialog : public CDialogEx
{
public:
CSettingsDialog(CWnd* pParent = nullptr);
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_SETTINGS_DIALOG };
#endif
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
afx_msg void OnBnClickedOk();
DECLARE_MESSAGE_MAP()
private:
CString m_name;
int m_interval;
};
تظهر العناصر التالية كثيراً في شيفرة مربّعات الحوار.
IDD_... معرّف مورد مربّع الحوار
IDC_... معرّف عنصر التحكّم
OnInitDialog معالجة التهيئة
DoDataExchange ربط عناصر التحكّم بالمتغيّرات الأعضاء
UpdateData مزامنة الشاشة مع المتغيّرات
ON_BN_CLICKED معالجة النقر على الزرّ
مربّع الحوار لا يعمل بالمظهر وحده، بل بمزيج من الموارد والمتغيّرات الأعضاء وخريطة الرسائل ومعالجة التهيئة.
13. DDX وDDV
من الأمور التي تظهر كثيراً في مربّعات حوار MFC هي DDX وDDV.
DDX = Dialog Data Exchange
DDV = Dialog Data Validation
DDX آليّة تربط عناصر التحكّم في مربّع الحوار بالمتغيّرات الأعضاء بلغة C++، وDDV آليّة تتحقّق من صحّة القيمة المُدخَلة.
void CSettingsDialog::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT_NAME, m_name);
DDX_Text(pDX, IDC_EDIT_INTERVAL, m_interval);
DDV_MinMaxInt(pDX, m_interval, 1, 3600);
}
عند استدعاء UpdateData(TRUE)، تُنعكَس قيم الإدخال في الشاشة على المتغيّرات الأعضاء.
void CSettingsDialog::OnBnClickedOk()
{
if (!UpdateData(TRUE))
{
return;
}
// هنا تحمل m_name وm_interval قيم الإدخال من الشاشة
SaveSettings(m_name, m_interval);
CDialogEx::OnOK();
}
بالعكس، عند استدعاء UpdateData(FALSE)، تُنعكَس قيم المتغيّرات الأعضاء في الشاشة.
BOOL CSettingsDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
m_name = _T("default");
m_interval = 60;
UpdateData(FALSE);
return TRUE;
}
عندما تكون قيم الإدخال في مربّع حوار MFC غير صحيحة، تحقّق من هذه النقاط.
هل DDX معرَّف داخل DoDataExchange
هل يُستدعى UpdateData(TRUE)
هل توقيت استدعاء UpdateData(FALSE) صحيح
هل يرفض DDV الإدخال
هل تتطابق معرّفات عناصر التحكّم مع الموارد
14. بنية Document/View
إحدى السمات الكبرى لـ MFC هي بنية Document/View.
هذه بنية تفصل بين البيانات التي يتعامل معها التطبيق وعرضها.
CDocument
يحتفظ بالبيانات
يتولّى قراءة الملفّات وكتابتها
يُخطر العروض المتعدّدة بالتحديثات
CView
يعرض البيانات
يتعامل مع تفاعل المستخدم
يدير الرسم وحالة التحديد
مثلاً، في محرّر نصوص، أو محرّر أشكال، أو أداة تحرير ملفّات إعدادات، أو تطبيق شبيه بـ CAD، يكون لفصل البيانات عن العرض معنى.
class CMyDocument : public CDocument
{
public:
std::vector<Item> m_items;
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
};
class CMyView : public CView
{
protected:
virtual void OnDraw(CDC* pDC);
CMyDocument* GetDocument() const;
};
من جهة العرض، يُحصَل على المستند ويُرسَم.
void CMyView::OnDraw(CDC* pDC)
{
CMyDocument* pDoc = GetDocument();
if (pDoc == nullptr)
{
return;
}
for (const auto& item : pDoc->m_items)
{
// ارسم باستخدام pDC
}
}
ميزة Document/View هي سهولة عرض البيانات نفسها في عروض متعدّدة.
مثلاً، يمكن عرض البيانات نفسها بهذه الطرق.
عرض جدوليّ
عرض بيانيّ (Graph)
عرض تفصيليّ
معاينة
عرض للطباعة
لكن استخدام Document/View في شاشة إعدادات بسيطة أو أداة صغيرة قد يجعل البنية تبدو ثقيلة دون داعٍ.
عند الصيانة، يساعد تحديد ما إذا كان التطبيق يستخدم Document/View أو محوره مربّع حوار منذ البداية في تسهيل تتبّع الشيفرة.
15. SDI وMDI
في MFC، تظهر كثيراً بنيتا SDI وMDI بالاشتراك مع Document/View.
SDI = Single Document Interface
MDI = Multiple Document Interface
SDI هو شكل يتعامل أساساً مع مستند واحد في إطار واحد.
النافذة الرئيسيّة
مستند واحد
عرض واحد أو أكثر
MDI هو شكل يحتوي على نوافذ فرعيّة متعدّدة داخل نافذة أب واحدة، تتعامل كلّ منها مع مستند.
إطار MDI الأب
إطار MDI الفرعيّ 1 -> المستند 1
إطار MDI الفرعيّ 2 -> المستند 2
إطار MDI الفرعيّ 3 -> المستند 3
في تطبيقات Windows القديمة، كان MDI يُستخدَم كثيراً.
عند الصيانة، يمكن تخمين البنية من اسم الفئة.
CFrameWnd إطار من نوع SDI
CMDIFrameWnd إطار MDI الأب
CMDIChildWnd إطار MDI الفرعيّ
CSingleDocTemplate قالب مستند لـ SDI
CMultiDocTemplate قالب مستند لـ MDI
في التطبيقات التي أُنشئت عبر معالج MFC (Wizard)، كثيراً ما توجد عمليّة تسجيل CSingleDocTemplate أو CMultiDocTemplate داخل InitInstance.
16. فهم ملفّات الموارد
في تطبيقات MFC، ملفّ .rc مهمّ جدّاً.
.rc هو ملفّ موارد Windows.
يُعرَّف فيه أشياء من هذا النوع.
قوالب مربّعات الحوار
القوائم
مفاتيح الاختصار (Accelerator Keys)
الأيقونات
الصور النقطيّة (Bitmaps)
جدول السلاسل النصّيّة
معلومات الإصدار
أشرطة الأدوات
كذلك، يُعرَّف في resource.h معرّفات الموارد.
#define IDD_SETTINGS_DIALOG 101
#define IDC_EDIT_NAME 1001
#define IDC_EDIT_INTERVAL 1002
#define ID_FILE_OPEN 32771
في شيفرة MFC، تُستخدَم هذه المعرّفات لربط الموارد بشيفرة C++.
DDX_Text(pDX, IDC_EDIT_NAME, m_name);
ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)
المشكلة الشائعة عند الصيانة هي عدم اتّساق معرّفات الموارد.
تغيّر معرّف في resource.h
تعارض المعرّفات عند دمج فرع آخر
عدم تطابق معرّف عنصر التحكّم في مربّع الحوار مع معرّف DDX
بقاء معرّف قائمة كان يُفترَض حذفه
تكرار معرّفات في جدول السلاسل النصّيّة
عند التحقيق في سلوك تطبيق MFC، يلزم النظر في .rc وresource.h معاً إلى جانب شيفرة C++.
17. Class Wizard والشيفرة المكتوبة يدويّاً
لـ MFC تاريخ وثيق الصلة بـ Class Wizard في Visual Studio.
باستخدام Class Wizard، يمكن توليد معالجات الرسائل، ومتغيّرات DDX، وتجاوزات الدوالّ الافتراضيّة (Virtual Functions) تلقائيّاً.
لذا، تبقى في شيفرة MFC آثار كثيرة من الشيفرة التي ولّدتها الأداة.
//{{AFX_DATA(CSettingsDialog)
//}}AFX_DATA
//{{AFX_MSG(CSettingsDialog)
//}}AFX_MSG
قد يختلف الشكل الظاهر والمولَّد في إصدارات Visual Studio الأحدث، لكن في قواعد الشيفرة القديمة، لا تزال علامات التعليق هذه موجودة أحياناً.
المهمّ عند الصيانة هو عدم كسر الحدّ الفاصل بين الشيفرة المولَّدة والشيفرة المكتوبة يدويّاً بشكل عشوائيّ.
لا تحذف خريطة الرسائل
لا تكسر مطابقة DDX
لا تغيّر معرّفات الموارد بلا مبالاة
لا تحذف تعليقات Class Wizard القديمة بلا داعٍ
لا يكفي في MFC أن تنجح الشيفرة في الترجمة (Compile) كـ C++ فقط. يلزم أيضاً الحفاظ إلى حدّ ما على الشكل الذي يتوقّعه محرّر الموارد (Resource Editor) وClass Wizard في Visual Studio.
18. CString والسلاسل النصّيّة
فئة السلاسل النصّيّة التي تظهر كثيراً في MFC هي CString.
CString name = _T("Komura");
CString message;
message.Format(_T("Hello, %s"), name.GetString());
CString فئة سلاسل نصّيّة متغيّرة الطول تُستخدَم كثيراً في شيفرة MFC/ATL. في C++ الحديثة، يُستخدَم std::string أو std::wstring في مواضع كثيرة، لكن في MFC يُستخدَم CString كثيراً بسبب توافقه مع الـ API وعناصر التحكّم.
عند الصيانة، يلزم مراعاة ترميز الأحرف.
CString يعادل CStringA أو CStringW حسب إعدادات المشروع
CStringA من نوع ANSI / MBCS
CStringW من نوع Unicode / UTF-16
LPCTSTR مؤشّر سلسلة نصّيّة قائم على TCHAR
LPCSTR من نوع char
LPCWSTR من نوع wchar_t
std::string عادةً من نوع char
std::wstring من نوع wchar_t
في تطبيقات Windows الحاليّة، من الأسلم أساساً افتراض Unicode، لكن قد تبقى في تطبيقات MFC القديمة شيفرة قائمة على افتراض MBCS.
CString text = _T("日本語");
std::wstring ws(text.GetString());
أخطاء تحويل السلاسل النصّيّة شائعة في صيانة تطبيقات MFC.
الحالات التي يجب الحذر منها بشكل خاصّ هي هذه.
قراءة ملفّات تفترض ترميز Shift_JIS
الانتقال إلى بناء Unicode
مكتبة DLL خارجيّة تتطلّب char*
COM يتطلّب BSTR
التحويل غير الحذر إلى std::string ما يسبِّب تشويه الأحرف
عند رؤية CString، لا تفكّر فيها كمجرّد «فئة سلاسل نصّيّة قديمة»، بل من المهمّ التحقّق منها إلى جانب إعداد مجموعة الأحرف (Character Set) للمشروع، وواجهات API الخارجيّة، وصيغة الملفّ.
19. CFile وCArchive
يمتلك MFC أيضاً فئات لعمليّات الملفّات والتسلسل (Serialization). الأكثر تمثيلاً هما CFile وCArchive.
CFile file;
if (file.Open(path, CFile::modeRead))
{
CArchive ar(&file, CArchive::load);
// اقرأ من ar
}
يُستخدَم CArchive كثيراً في آليّة التسلسل الخاصّة بـ MFC.
في الفئات المشتقّة من CDocument، يُجاوَز (Override) Serialize أحياناً لكتابة القراءة والحفظ في الدالّة نفسها.
void CMyDocument::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ar << m_title;
ar << static_cast<int>(m_items.size());
for (const auto& item : m_items)
{
ar << item.Name;
ar << item.Value;
}
}
else
{
int count = 0;
ar >> m_title;
ar >> count;
m_items.clear();
for (int i = 0; i < count; ++i)
{
Item item;
ar >> item.Name;
ar >> item.Value;
m_items.push_back(item);
}
}
}
تسلسل MFC مريح، لكنّه يتطلّب حذراً في التشغيل طويل الأمد.
التوافق مع صيغ الملفّات القديمة
إدارة رقم الإصدار
الاستعادة عند فشل القراءة
معالجة الاستثناءات
ترميز الأحرف
ترتيب البايتات (Endianness)
هل يُحفَظ البنية (Struct) كما هي دون تحويل
في تطبيقات MFC التي تستخدم صيغة ثنائيّة (Binary) خاصّة منذ زمن طويل، قد تكون Serialize هي المواصفات الفعليّة للملفّ.
في هذه الحالة، من الأفضل دائماً تجهيز بيانات اختبار لقراءة الملفّات القائمة قبل تغيير الشيفرة.
20. رسم GDI وCDC
في رسم شاشات MFC، تُستخدَم كثيراً الفئة CDC للتعامل مع سياق الجهاز (Device Context) الخاصّ بـ Windows. في CView::OnDraw، يُمرَّر CDC* كوسيط.
void CMyView::OnDraw(CDC* pDC)
{
pDC->TextOut(10, 10, _T("Hello MFC"));
pDC->Rectangle(10, 40, 200, 120);
}
عند استخدام القلم (Pen) أو الفرشاة (Brush)، احذر عند الاختيار (Select) والاستعادة (Restore).
void CMyView::OnDraw(CDC* pDC)
{
CPen pen(PS_SOLID, 1, RGB(0, 0, 0));
CPen* pOldPen = pDC->SelectObject(&pen);
pDC->MoveTo(10, 10);
pDC->LineTo(100, 100);
pDC->SelectObject(pOldPen);
}
حول كائنات GDI، تكون هذه الأخطاء مشكلة.
عدم استعادة الكائن الأصليّ بعد استدعاء SelectObject
إنشاء عدد كبير من كائنات GDI دون إتلافها
الخلط بين دوريّ OnPaint وOnDraw
الوميض (Flickering) بسبب عدم استخدام التخزين المؤقّت المزدوج (Double Buffering)
انكسار الرسم القائم على افتراض بكسل ثابت عند DPI العالي
في أخطاء رسم MFC، يلزم التحقّق ليس فقط من منطق C++، بل أيضاً من موارد GDI الخاصّة بـ Windows، وتوقيت إعادة الرسم، وDPI، وحجم الخطّ.
21. مربّعات الحوار النمطيّة وغير النمطيّة
في MFC، يلزم الحذر أيضاً في طريقة عرض مربّعات الحوار.
يُعرَض مربّع الحوار النمطيّ (Modal) عبر DoModal.
CSettingsDialog dlg(this);
if (dlg.DoModal() == IDOK)
{
// المعالجة عند الضغط على OK
}
في هذه الحالة، ينتظر المستدعي حتّى يُغلَق مربّع الحوار.
أمّا مربّع الحوار غير النمطيّ (Modeless)، فتعود معالجة المستدعي حتّى بعد إنشائه.
m_pToolDialog = new CToolDialog(this);
m_pToolDialog->Create(IDD_TOOL_DIALOG, this);
m_pToolDialog->ShowWindow(SW_SHOW);
في مربّعات الحوار غير النمطيّة، إدارة العمر (Lifetime) مهمّة.
متى يُحذَف (delete) مربّع الحوار الذي أُنشئ بـ new
هل يُتلَف النافذة الأب أوّلاً
هل يُستخدَم PostNcDestroy في جانب مربّع الحوار
هل يُنشأ مربّع الحوار مرّتين
هل يبقى مؤشّر عالقاً بعد الإغلاق
في التحقيق في أعطال (Crash) MFC، قد تكون مشكلة عمر مربّعات الحوار غير النمطيّة هي السبب.
22. عمر كائنات C++ ومقابض Windows
من الأمور المهمّة جدّاً في MFC هو اختلاف عمر كائنات C++ عن عمر مقابض Windows. فمثلاً، CWnd كائن C++، لكن النافذة الفعليّة يديرها Windows كـ HWND، وهذان الاثنان لا يُنشآن ويُتلَفان معاً دائماً.
كائن CWnd موجود لكنّ HWND لم يُنشأ بعد
HWND أُتلِف لكنّ كائن CWnd لا يزال موجوداً
تمّ إنشاء غلاف CWnd مؤقّت
تُستبدَل المقابض عبر Attach/Detach
مثلاً، هذه الشيفرة تحتاج حذراً.
CWnd* pWnd = GetDlgItem(IDC_SOME_CONTROL);
// حفظ pWnd في عضو (Member) لاستخدامه لاحقاً
عند الاحتفاظ بالمؤشّر الذي يُحصَل عليه من GetDlgItem لمدّة طويلة، هناك خطر الإشارة إليه بعد إتلاف النافذة.
إن لزم الأمر، فإنّ استدعاء GetDlgItem في كلّ مرّة، أو إدارة متغيّر عضو خاصّ بعنصر التحكّم عبر DDX، قد يكون أسلم.
DDX_Control(pDX, IDC_LIST_ITEMS, m_listItems);
في MFC، وجود المؤشّر بقيمة غير null لا يعني بالضرورة أنّه آمن.
if (m_pDialog != nullptr && ::IsWindow(m_pDialog->GetSafeHwnd()))
{
m_pDialog->SetWindowText(_T("Running"));
}
هذا الحسّ مهمّ جدّاً في صيانة MFC.
23. مؤشّرات الترابط وتحديث واجهة المستخدم
واجهة مستخدم Windows يجب أساساً التعامل معها من مؤشّر الترابط (Thread) الذي أُنشئت فيه، والأمر نفسه في تطبيقات MFC. فمعالجة عناصر التحكّم في واجهة المستخدم مباشرةً من مؤشّر ترابط عامل (Worker Thread) تسبِّب سلوكاً غير مستقرّ أو أعطالاً.
مثال يجب تجنّبه.
UINT WorkerThreadProc(LPVOID pParam)
{
CMyDialog* pDlg = static_cast<CMyDialog*>(pParam);
// تجنّب لمس واجهة المستخدم مباشرةً من مؤشّر ترابط عامل
pDlg->SetDlgItemText(IDC_STATUS, _T("Done"));
return 0;
}
عموماً، يُخطَر مؤشّر ترابط واجهة المستخدم عبر PostMessage أو ما شابه.
constexpr UINT WM_APP_WORK_DONE = WM_APP + 1;
UINT WorkerThreadProc(LPVOID pParam)
{
HWND hWnd = static_cast<HWND>(pParam);
// معالجة ثقيلة
::PostMessage(hWnd, WM_APP_WORK_DONE, 0, 0);
return 0;
}
من جهة واجهة المستخدم، تُستقبَل عبر خريطة الرسائل.
BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
ON_MESSAGE(WM_APP_WORK_DONE, &CMyDialog::OnWorkDone)
END_MESSAGE_MAP()
LRESULT CMyDialog::OnWorkDone(WPARAM, LPARAM)
{
SetDlgItemText(IDC_STATUS, _T("Done"));
return 0;
}
حول مؤشّرات الترابط في MFC، تحقّق ممّا يلي.
هل تُلمَس واجهة المستخدم مباشرةً من مؤشّر ترابط عامل
هل يُستدعى PostMessage بعد إتلاف النافذة
هل يُوقَف مؤشّر ترابط واجهة المستخدم أثناء انتظار انتهاء مؤشّر ترابط آخر
هل قفل البيانات المشترَكة مناسب
هل هناك سوء فهم لقيمة العودة وعمر AfxBeginThread
24. مكتبة MFC DLL وحالة الوحدة (Module State)
عند إنشاء مكتبة DLL بلغة MFC، يظهر مفهوم حالة الوحدة (Module State).
عند تحميل موارد من مكتبة MFC DLL، أو عرض مربّع حوار، أو إنشاء مكتبة DLL موسَّعة (Extension DLL)، تصبح مشكلة معرفة موارد أيّ وحدة سيُنظَر إليها.
في مدخل الدوالّ الخاصّة بمكتبة MFC DLL، قد تجد وحدة ماكرو كهذه.
AFX_MANAGE_STATE(AfxGetStaticModuleState());
هذا يجعل MFC يستخدم حالة الوحدة الصحيحة.
نسيان هذه الوحدة يؤدّي إلى مشكلات كهذه.
تعذّر العثور على موارد مربّع الحوار داخل DLL
تُقرَأ موارد السلاسل النصّيّة من وحدة أخرى
تعذّر العثور على الأيقونات أو القوائم
يعمل فقط في وضع التصحيح (Debug) وينكسر في الإصدار (Release)
عند صيانة مكتبة MFC DLL، من المهمّ ترتيب العلاقة بين ملفّ EXE ومكتبة DLL العاديّة ومكتبة DLL الموسَّعة ومكتبة DLL الموارد.
خصوصاً عند استدعاء مكتبة MFC DLL من تطبيق غير MFC، أو في بنية إضافات (Plugin)، يلزم الحذر.
25. الربط الساكن لـ MFC أم استخدامه عبر DLL مشترَكة
في تطبيقات MFC، توجد في إعدادات المشروع خاصيّة «Use of MFC».
الخياران التمثيليّان هما هذان.
Use MFC in a Shared DLL
Use MFC in a Static Library
عند استخدام DLL مشترَكة، يلزم توفّر بيئة تشغيل MFC وبيئة تشغيل Visual C++ المناسبة لبيئة التنفيذ.
في حالة الربط الساكن (Static Linking)، قد تبدو المخرجات المُوزَّعة بسيطة، لكن يلزم التفكير في حجم الملفّ التنفيذيّ، وتحديثه، واستيعاب تصحيحات الأمان، وشروط الترخيص وإعادة التوزيع.
لا يوجد خيار صحيح دائماً.
عوامل القرار هي هذه.
هل يمكن تثبيت Visual C++ Redistributable في وجهة التوزيع
هل تريد جعل التطبيق يقترب من ملفّ exe واحد
كيف ستُطبَّق تحديثات الأمان
هل تشترك تطبيقات متعدّدة في بيئة التشغيل نفسها
هل يمكن توفير مثبِّت (Installer)
ما هو إصدار Windows المستهدَف
عند الصيانة، من المهمّ التحقّق أوّلاً من الإعداد الحاليّ.
Configuration Properties
General
Use of MFC
بالإضافة إلى ذلك، تحقّق أيضاً من إعداد Runtime Library.
/MD Multi-threaded DLL
/MDd Multi-threaded Debug DLL
/MT Multi-threaded
/MTd Multi-threaded Debug
عند اختلاط إعدادات ربط MFC وCRT، قد تظهر مشكلات في تخصيص/تحرير الذاكرة عند حدود المكتبة.
26. Unicode وMBCS وTCHAR
في شيفرة MFC القديمة، تظهر كثيراً TCHAR وLPCTSTR ووحدة الماكرو _T().
CString title = _T("設定");
SetWindowText(title);
هذا أسلوب كتابة للتوافق مع كلّ من بناء Unicode وبناء MBCS.
بناء Unicode
TCHAR -> wchar_t
LPCTSTR -> const wchar_t*
_T("...") -> L"..."
بناء MBCS
TCHAR -> char
LPCTSTR -> const char*
_T("...") -> "..."
الآن بناء Unicode هو الشائع، لكن في التطبيقات القديمة، قد تبقى معالجة قائمة على افتراض MBCS.
خصوصاً في الملفّات الخارجيّة، وبروتوكولات الاتّصال، ومكتبات DLL القديمة، والاتّصال بقواعد البيانات، والاتّصال التسلسليّ (Serial)، يلزم التحقّق من افتراض ترميز الأحرف.
المهمّ هو عدم اعتبار التحويل إلى Unicode مجرّد عمليّة استبدال بسيطة.
هل حجم مصفوفة char بعدد البايتات أم بعدد الأحرف
هل يُستخدَم strlen
هل يُستخدَم sizeof(buffer) كعدد أحرف
هل تستقبل الواجهة الخارجيّة UTF-16 أم Shift_JIS
هل يجوز تغيير صيغة حفظ الملفّ
عند إصلاح السلاسل النصّيّة في MFC، تحقّق ليس فقط من عرض الشاشة، بل أيضاً من توافق الملفّات والتكامل الخارجيّ.
27. MFC وCOM/OLE/ActiveX
استُخدم MFC أيضاً في تطبيقات وثيقة الصلة بـ COM وOLE وActiveX.
في تطبيقات الأعمال القديمة، قد تبقى عناصر من هذا النوع.
OLE Automation
ActiveX Control
خادم COM
عميل COM
IDispatch
BSTR
VARIANT
COleDispatchDriver
COleVariant
في معالجة بدء تشغيل تطبيق MFC، قد تظهر شيفرة كهذه.
if (!AfxOleInit())
{
AfxMessageBox(_T("OLE initialization failed"));
return FALSE;
}
عند استخدام COM/OLE، قد يبدو الأمر مشكلة في MFC، لكنّه في الواقع قد يكون سببه تهيئة COM، أو نموذج مؤشّر الترابط (Threading Model)، أو عدّاد المراجع (Reference Count)، أو معلومات التسجيل، أو اختلاف 32bit/64bit.
يلزم الحذر بشكل خاصّ من ActiveX ومكوّنات COM ذات 32bit.
تطبيق MFC ذو 32bit يستخدم COM من نوع 32bit
تطبيق MFC ذو 64bit يستخدم COM من نوع 64bit
تسجيل COM لـ 32bit و64bit منفصل
قد لا يدعم ActiveX القديم بنية 64bit
عند تحويل تطبيق MFC إلى x64، تحقّق دائماً من الاعتماد على COM/OLE إلى جانب شيفرة واجهة المستخدم.
28. دعم DPI العالي وWindows الحديثة
عند تشغيل تطبيق MFC قديم على Windows حديثة، قد ينكسر العرض في بيئة DPI عالية.
مثلاً، مشكلات من هذا النوع.
انقطاع النصّ
صِغَر الأزرار أكثر من اللازم
انزياح الرسم القائم على بكسل ثابت
انكسار العرض عند اختلاف نسبة التكبير بين شاشات متعدّدة
ضبابيّة الصور النقطيّة القديمة
ازدحام تخطيط مربّع الحوار
في تطبيقات سطح مكتب Windows، يلزم أن يُصرِّح التطبيق بوضع دعم DPI.
في تطبيقات MFC أيضاً، يلزم التحقّق من البيان (Manifest)، والموارد، وشيفرة الرسم، والخطّ، والتخطيط.
خصوصاً، الشيفرة التي تكتب الإحداثيّات الثابتة مباشرةً كهذه تصبح مشكلة بسهولة عند DPI العالي.
pDC->TextOut(10, 10, _T("Status"));
pDC->Rectangle(10, 40, 200, 80);
الإحداثيّات القائمة على افتراض بكسل ثابت ينكسر مظهرها عند تغيّر DPI.
نقاط التحقّق عند الصيانة هي هذه.
إعداد DPI في بيان التطبيق (Manifest)
خطّ موارد مربّع الحوار
الرسم القائم على بكسل ثابت
دقّة موارد الصور
السلوك على شاشات متعدّدة
العرض على Windows 10 / Windows 11
دعم DPI العالي في MFC لا ينتهي بمجرّد تغيير إعدادات المشروع فقط. في الواجهات القديمة، يلزم التحقّق الفعليّ من الشاشة وتصحيح التخطيط.
29. معالجة الاستثناءات والأخطاء
يمتلك MFC فئات استثناءات (Exception) وأسلوب معالجة أخطاء خاصّاً به.
في الشيفرة القديمة، قد تجد وحدات ماكرو كهذه.
TRY
{
// المعالجة
}
CATCH(CFileException, e)
{
e->ReportError();
}
END_CATCH
وتوجد شيفرة تختلط فيها مع try / catch الخاصّة بـ C++ الحديثة.
try
{
DoSomething();
}
catch (const std::exception& ex)
{
// إخراج السجلّ
}
النقاط التي يجب الحذر منها في صيانة MFC هي هذه.
هل تختلط استثناءات MFC مع استثناءات C++ القياسيّة
هل هناك سوء فهم لعمر كائن الاستثناء
هل تُفهَم وحدات ماكرو THROW/CATCH القديمة
هل يختلط خطأ القيمة المُعادة مع الاستثناءات
هل ينتهي الأمر بـ AfxMessageBox فقط دون ترك سجلّ
في تطبيقات الأعمال، من المهمّ عدم الاكتفاء بعرض رسالة خطأ على الشاشة، بل ترك السجلّ، وسجلّ العمليّات، وقيم الإدخال، وحالة الاتّصال الخارجيّ.
في تطبيقات MFC القديمة، قد ينتهي الخطأ بمجرّد AfxMessageBox فقط.
AfxMessageBox(_T("فشل الحفظ."));
لرفع قابليّة الصيانة، من الأفضل فصل عرض واجهة المستخدم عن تسجيل السجلّ.
LogError(_T("Save failed"), path);
AfxMessageBox(_T("فشل الحفظ. يرجى مراجعة السجلّ."));
30. كيفيّة التعايش بين MFC وC++ الحديثة
مجرّد صيانتك لتطبيق MFC لا يعني أنّك بحاجة لمواءمة كلّ شيء مع أسلوب C++ القديم.
يمكن احترام أسلوب MFC في طبقة واجهة المستخدم، مع تنظيم منطق النطاق (Domain Logic) والمعالجة الحسابيّة بلغة C++ الحديثة.
مثلاً، يمكن الفصل كما يلي.
طبقة MFC
CDialog
CView
CDocument
CString
خريطة الرسائل
التعامل مع الموارد
الطبقة غير المرتبطة بـ MFC
std::string / std::wstring
std::vector
std::optional
std::variant
std::filesystem
فئات قابلة للاختبار الوحدويّ
منطق الأعمال
الشكل السيّئ هو أن يكون كلّ المعالجة محشوَّة في فئة مربّع الحوار.
void CMainDialog::OnBnClickedExecute()
{
// الحصول على الإدخال
// قراءة الملفّات
// الاتّصال
// الحساب
// تحديث قاعدة البيانات
// تحديث الشاشة
// إخراج السجلّ
// معالجة الاستثناءات
}
شيفرة من هذا النوع يصعب تغييرها، ويصعب اختبارها، ويصعب التحقيق في الأخطاء أيضاً.
للتحسين، أخرِج المنطق من فئة MFC.
void CMainDialog::OnBnClickedExecute()
{
if (!UpdateData(TRUE))
{
return;
}
ExecuteRequest request;
request.Name = ToStdWString(m_name);
request.Interval = m_interval;
ExecuteResult result = m_service.Execute(request);
m_status = ToCString(result.Message);
UpdateData(FALSE);
}
بهذا يمكن اختبار m_service.Execute من دون MFC.
أفضل تحسين فعّال في صيانة أصول MFC القائمة هو إخراج المنطق تدريجيّاً من فئة واجهة المستخدم.
31. جعل شيفرة MFC قابلة للاختبار
غالباً ما يصعب اختبار تطبيق MFC وحدويّاً (Unit Test) كما هو.
السبب هو أنّ واجهة المستخدم وWin32 والملفّات والاتّصال وقاعدة البيانات والحالة العامّة (Global State) تميل إلى الاقتران الوثيق (Tight Coupling).
طريقة التفكير لجعله قابلاً للاختبار هي هذه.
لا تحاول اختبار CDialog أو CView مباشرةً
أخرِج أوّلاً المنطق غير المرتبط بواجهة المستخدم
حوِّل أنواع MFC عند الحدود
اجعل الملفّات والاتّصال واجهات (Interfaces)
اجعل معالجات أحداث الشاشة رقيقة
مثلاً، انقل المعالجة إلى فئة C++ خالصة.
class PriceCalculator
{
public:
int CalculateTotal(const std::vector<int>& prices) const
{
int total = 0;
for (int price : prices)
{
total += price;
}
return total;
}
};
يتولّى جانب MFC الإدخال والإخراج فقط.
void CPriceDialog::OnBnClickedCalculate()
{
if (!UpdateData(TRUE))
{
return;
}
std::vector<int> prices = ParsePrices(ToStdWString(m_input));
int total = m_calculator.CalculateTotal(prices);
m_result.Format(_T("%d"), total);
UpdateData(FALSE);
}
بهذه البنية، يمكن اختبار PriceCalculator وParsePrices بأطر اختبار C++ العاديّة.
لا حاجة لإعادة بناء تطبيق MFC ككلّ دفعة واحدة، بل يكفي في البداية إخراج المعالجة القابلة للاختبار من داخل معالجات الأحداث ليكون له أثر.
32. تثبيت بيئة البناء
في صيانة تطبيقات MFC، تثبيت بيئة البناء مهمّ.
في قواعد الشيفرة القديمة، قد تتغيّر نتيجة البناء بسبب الاختلافات التالية.
إصدار Visual Studio
إصدار مجموعة أدوات MSVC (Toolset)
إصدار Windows SDK
وجود مكوّنات MFC/ATL من عدمه
x86 / x64 / ARM64
Debug / Release
Unicode / MBCS
الربط الساكن لـ MFC / DLL مشترَكة
إعدادات مكتبة وقت التشغيل (Runtime Library)
الترويسات المُصرَّفة مسبقاً (Precompiled Headers)
في تطبيقات MFC، قد تتركّز تبعيّات كثيرة في stdafx.h أو pch.h.
#include "framework.h"
#include "MyApp.h"
قد ينكسر البناء لأسباب مثل اختلاف إعداد الترجمة في ملفّ واحد فقط، أو انزياح إعداد الترويسات المُصرَّفة مسبقاً.
في مشاريع الصيانة، توثيق هذه المعلومات في ملفّ README يسهِّل الأمور لاحقاً.
إصدار Visual Studio المطلوب
حمولات العمل (Workloads) والمكوّنات الفرديّة المطلوبة
Windows SDK المطلوب
المنصّة المستهدَفة
طريقة ربط MFC
خطوات البناء
طريقة تشغيل CI
طريقة إنشاء المخرجات المُوزَّعة
التخرّج من «يمكن بناؤه على جهازي الشخصيّ فقط» هو الخطوة الأولى في صيانة MFC.
33. بناء MFC عبر CI
يمكن أيضاً بناء تطبيقات MFC عبر CI.
لكن يلزم توفّر مكوّنات MFC في بيئة CI.
عند استخدام Visual Studio Build Tools، يلزم تثبيته بتحديد معرّف مكوّن MFC/ATL.
النقاط التي يجب التحقّق منها هي هذه.
هل MFC مضمَّن في Build Tools
هل تتطابق مجموعة الأدوات المستهدَفة مع المشروع
هل Windows SDK مثبَّت
هل جرى التحقّق من بناء x86 وبناء x64 معاً
هل يعمل مُصرِّف الموارد (Resource Compiler)
هل هناك معالجة توقيع (Signing)
هل يشمل CI أيضاً توليد المثبِّت (Installer)
ليس تشغيل اختبارات واجهة المستخدم آليّاً في تطبيقات MFC أمراً سهلاً، لكن على الأقلّ هذا القدر من الأتمتة فعّال.
بناء Debug / Release
بناء x86 / x64
التحليل الساكن (Static Analysis)
الاختبار الوحدويّ (Unit Test)
إنشاء المثبِّت (Installer)
حفظ قيمة التجزئة (Hash) للمخرجات
التحقّق من مكتبات DLL المعتمَد عليها
في الصيانة طويلة الأمد، مجرّد الحفاظ على قابليّة البناء له قيمة كبيرة.
34. أين تنظر عند تصحيح أخطاء تطبيق MFC
عند تتبّع عطل في تطبيق MFC، يكون النظر بهذا الترتيب أكثر كفاءة.
1. ما هي الشاشة المعنيّة
2. هل هي مربّع حوار، أم View، أم Frame
3. ما معرّف المورد المقابل للعمليّة
4. إلى أيّ دالّة تتّجه خريطة الرسائل
5. هل اتّجاه UpdateData صحيح
6. إن كان Document/View، فما حالة Document
7. هل ينتقل الأمر إلى فئة أخرى عبر توجيه الأوامر
8. هل تُلمَس واجهة المستخدم من مؤشّر ترابط عامل
9. هل تُبتلَع الاستثناءات أو الأخطاء بمجرّد AfxMessageBox
10. هل توجد مشكلات عدم تهيئة أو عمر خاصّة ببناء الإصدار (Release)
مثلاً، في عطل من نوع «لا يحدث شيء عند الضغط على الزرّ»، تحقّق من هذا.
هل معرّف IDC الخاصّ بالزرّ صحيح
هل يوجد ON_BN_CLICKED
هل توقيع الدالّة المعالِجة صحيح
هل مورد مربّع الحوار مختلف عمّا هو متوقَّع
هل الزرّ معطَّل
هل يفشل UpdateData في منتصف المعالجة
هل تُبتلَع الاستثناءات
وفي «تعذّر الضغط على القائمة»، تحقّق من هذا.
هل هو معطَّل عبر ON_UPDATE_COMMAND_UI
هل تتكرّر معرّفات الأوامر
هل العرض النشط كما هو متوقَّع
في أيّ من Frame/View/Document/App توجد الدالّة المعالِجة
بما أنّ MFC يربط الحدث الظاهر بالمعالجة الفعليّة عبر وحدات ماكرو وتوجيه، فمن الأسهل الفهم برسم مسار الاستدعاء كرسم بيانيّ حتّى الاعتياد.
35. المطبّات الشائعة
نلخّص المطبّات الشائعة في صيانة MFC.
البحث عن استدعاءات الدوالّ فقط دون النظر إلى خريطة الرسائل
افتراض صلاحيّة CWnd* لمجرّد أنّه غير null
الخلط بين عمر HWND وعمر كائن C++
الخطأ في اتّجاه UpdateData(TRUE/FALSE)
عدم ملاحظة التعطيل عبر ON_UPDATE_COMMAND_UI
عدم ملاحظة تعارض المعرّفات في resource.h
لمس واجهة المستخدم مباشرةً من مؤشّر ترابط عامل
تشويه الأحرف عند التحويل بين CString وstd::string
كسر الشيفرة القائمة على افتراض MBCS عند التحويل إلى Unicode
نسيان AFX_MANAGE_STATE في مكتبة MFC DLL
كسر COM/ActiveX القائم على افتراض x86 عند التحويل إلى x64
الخطأ في اختيار/تحرير كائنات GDI
انكسار تخطيط الإحداثيّات الثابتة عند DPI العالي
قد لا تُفهَم أعطال MFC بالنظر إلى قواعد C++ فقط، ويلزم النظر أيضاً إلى رسائل Windows والموارد والمقابض والوحدات وإعدادات بيئة التشغيل.
36. هل ينبغي اختيار MFC في تطوير جديد
في التطوير الجديد الكامل، يجب التفكير بحذر فيما إذا كان اختيار MFC مناسباً.
إن كان هناك سبب لاختيار MFC، فهو في هذه الحالات.
الحاجة إلى تكامل وثيق مع شيفرة MFC القائمة
الرغبة في إعادة استخدام مكوّنات أو شاشات MFC القائمة
الحاجة إلى تحكّم قريب جدّاً من Win32/GDI/COM
توفّر مهارة كافية لصيانة MFC داخل الشركة
كون الهدف مقتصراً على سطح مكتب Windows
الحاجة إلى التوسّع بـ MFC أوّلاً ضمن خطّة انتقال طويلة الأمد
بالعكس، في الحالات التالية، من الأفضل النظر في خيارات أخرى.
الرغبة في بناء واجهة مستخدم حديثة
الحاجة إلى تخطيطات مرنة أو رسوم متحرّكة
التركيز على التكامل مع الويب أو السحابة
إعطاء الأولويّة لسهولة الاختبار
الرغبة في اختيار تقنيّة يسهل على المطوّرين الشباب المشاركة فيها
الحاجة إلى دعم عبر المنصّات (Cross-platform)
إعطاء الأولويّة لإمكانيّة الوصول ودعم DPI العالي منذ البداية
MFC ليس تقنيّة «لا قيمة لتعلّمها الآن» ولا تقنيّة «تُختار لأنّها جديدة»، بل تقنيّة لمواجهة أصول Windows الأصليّة القائمة.
37. طريقة التفكير عند الانتقال من MFC
عند الرغبة في نقل تطبيق MFC إلى تقنيّة أخرى، فإنّ استهداف إعادة كتابة شاملة دفعة واحدة يسهل أن يفشل.
أوّلاً، فكِّك التطبيق على هذا النحو.
واجهة المستخدم (UI)
منطق الأعمال
صيغة الملفّ
معالجة الاتّصال
معالجة قاعدة البيانات
التحكّم بالأجهزة
الطباعة
تكامل COM/OLE
إدارة الإعدادات
السجلّ (Log)
من بين هذه، الأكثر اعتماداً على MFC هي واجهة المستخدم.
في المقابل، منطق الأعمال ومعالجة الملفّات لديهما إمكانيّة الفصل.
الترتيب الواقعيّ للانتقال هو هذا.
1. إعادة إنتاج بيئة البناء
2. تثبيت السلوك القائم عبر بيانات اختبار
3. إخراج المنطق من معالِجات أحداث واجهة المستخدم
4. نقله إلى مكتبة C++ غير مرتبطة بـ MFC
5. إضافة اختبارات آليّة
6. توثيق المواصفات الخارجيّة
7. الاستبدال التدريجيّ بدءاً من الشاشات اللازمة
جعل الهدف «إخراج المنطق المهمّ المحتجَز داخل MFC» بدل جعل الهدف «التخلّص من MFC» بحدّ ذاته، يجعل النجاح أسهل.
38. مدخل قراءة شيفرة MFC
عند قراءة مشروع MFC قائم لأوّل مرّة، تبدأ بالنظر إلى هذه الملفّات.
*.vcxproj
انظر إلى مجموعة الأدوات وإعدادات MFC ومجموعة الأحرف وإعدادات وقت التشغيل
resource.h
انظر إلى معرّفات الموارد
*.rc
انظر إلى مربّعات الحوار والقوائم والسلاسل النصّيّة والأيقونات
*App.cpp / *App.h
انظر إلى الفئة المشتقّة من CWinApp وإلى InitInstance
MainFrm.cpp / MainFrm.h
انظر إلى الإطار الرئيسيّ والقوائم/أشرطة الأدوات
*Doc.cpp / *Doc.h
إن كان Document/View، انظر إلى بنية البيانات ومعالجة الحفظ
*View.cpp / *View.h
انظر إلى الرسم وتفاعل المستخدم
*Dlg.cpp / *Dlg.h
انظر إلى مربّع الحوار وDDX ومعالجة الأزرار
تالياً، كلمات البحث الشائعة الاستخدام.
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_UPDATE_COMMAND_UI
ON_BN_CLICKED
DoDataExchange
UpdateData
OnInitDialog
OnDraw
Serialize
AfxMessageBox
AfxBeginThread
AFX_MANAGE_STATE
البحث عن هذه الكلمات يسهِّل رؤية حركة التطبيق.
39. سياسة التصميم عند صيانة MFC
عند صيانة أصول MFC القائمة لمدّة طويلة، هذه السياسات فعّالة.
اجعل معالِجات أحداث واجهة المستخدم رقيقة
لا تُسرِّب CString أو CWnd إلى الطبقة غير المرتبطة بواجهة المستخدم أكثر من اللازم
انقل منطق الأعمال إلى فئات C++ عاديّة
أنشئ اختبارات توافق لصيغة الملفّ
وضِّح الفروقات بين x86/x64
راجع أيّ تغيير في معرّفات الموارد
تحقّق من حالة الوحدة في مكتبة MFC DLL
رتِّب السجلّ (Log)
ثبِّت البناء عبر CI
تحقّق دوريّاً من الشاشة عند DPI العالي وعلى Windows 11
من المهمّ بشكل خاصّ عدم حشو معالجة كثيرة في CDialog أو CView.
اجعل فئات شاشة MFC مركَّزة على الإدخال والعرض وتوصيل الأحداث.
أخذ القيمة من الشاشة
تمريرها إلى الخدمة (Service)
انعكاس النتيجة على الشاشة
بالحفاظ على هذا القدر، يصبح MFC أيضاً قابلاً للصيانة بدرجة كبيرة.
40. قائمة تحقّق للعمل الفعليّ
قائمة تحقّق عند التعامل مع تطبيق MFC.
هل إصدار Visual Studio واضح
هل مكوّنات MFC/ATL مثبَّتة
هل الهدف x86/x64 واضح
هل إعدادات Unicode/MBCS معروفة
هل MFC مرتبط ارتباطاً ساكناً أم عبر DLL مشترَكة
ما هو Visual C++ Redistributable المطلوب
هل resource.h وملفّ .rc ضمن نطاق المراجعة
هل تُتَبَّع مسارات الأحداث عبر خريطة الرسائل
هل اتّجاه UpdateData صحيح
هل تُستخدَم بنية Document/View
هل إدارة عمر مربّع الحوار غير النمطيّ آمنة
هل تُلمَس واجهة المستخدم مباشرةً من مؤشّر ترابط عامل
هل توجد مواضع تحتاج AFX_MANAGE_STATE في مكتبة MFC DLL
هل جرى التحقّق من الشاشة في بيئة DPI عالية
هل تدعم اعتماديّات COM/ActiveX كلاً من 32bit/64bit
هل يُحفَظ السجلّ
هل يمكن اختبار المنطق غير المرتبط بواجهة المستخدم
MFC يبدو فريداً حتّى الاعتياد عليه، لكن بمجرّد معرفة أين يجب النظر، يصبح قابلاً للقراءة بانتظام كبير.
41. الخلاصة
MFC إطار عمل عريق لبناء تطبيقات سطح مكتب Windows الأصليّة بلغة C++. لم يعد التيّار الرئيسيّ للتطوير الجديد، لكنّه لا يزال تقنيّة مهمّة في صيانة الأصول القائمة، وتحديث بيئة البناء، وإضافة الميزات، والانتقال التدريجيّ.
النقاط المهمّة بشكل خاصّ لفهم MFC هي هذه.
MFC يجعل التعامل مع Win32 API أسهل من C++
CWinApp يدير التطبيق ككلّ
عمر CWnd وHWND ليسا متطابقَين
خريطة الرسائل تربط الأحداث بالدوالّ
Document/View آليّة تفصل البيانات عن العرض
DDX/DDV يُستخدَمان لمزامنة قيم مربّع الحوار والتحقّق منها
ملفّات الموارد وresource.h مهمّة جدّاً
افهم CString وTCHAR مقترنَين بإعداد ترميز الأحرف
انتبه إلى حالة الوحدة في مكتبة MFC DLL
تستحقّ تطبيقات MFC القديمة إعادة النظر من زاوية DPI العالي وx64 وCI والاختبار
عند التعامل مع MFC، من المهمّ فهم البنية أوّلاً بدلاً من الحكم بأنّه «سيّئ لأنّه قديم». قد تحتوي تطبيقات MFC القائمة على معرفة أعمال طويلة الأمد، ومواصفات خاصّة بكلّ عميل، وتكامل مع الأجهزة، وتوافق الملفّات. للحفاظ على هذه القيمة مع جعلها قابلة للصيانة تدريجيّاً، فإنّ فهم أسلوب MFC، مع إخراج المنطق غير المرتبط بواجهة المستخدم، وترتيب البناء والاختبار، هو النهج الواقعيّ.
وإن أردنا تلخيص الأمر في جملة واحدة، فسيكون هكذا.
أساس للتعامل مع آليّات تطبيقات Windows الأصليّة عبر فئات C++ وإطار عمل.
بامتلاك وجهة النظر هذه، لا يصبح MFC مجرّد تقنيّة قديمة، بل دليلاً لقراءة أصول Windows القائمة بأمان.
المراجع
- مجموعة شيفرات مرجعيّة لمقاطع هذا المقال مرتّبة حسب الفصول - komurasoft-blog-samples (GitHub)
- MFC Desktop Applications - Microsoft Learn
- MFC と ATL - Microsoft Learn
- Creating an MFC Application - Microsoft Learn
- CWinApp Class - Microsoft Learn
- CWinApp: The Application Class - Microsoft Learn
- Mapping Messages - Microsoft Learn
- Document-View Architecture - Microsoft Learn
- Dialog Data Exchange and Validation - Microsoft Learn
- MFC Library Versions - Microsoft Learn
- Redistribute the MFC Library - Microsoft Learn
- Visual Studio Build Tools workload and component IDs - Microsoft Learn
- High DPI Desktop Application Development on Windows - Microsoft Learn
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
التعامل الصحيح مع رموز انتحال الهوية في Windows ── استعارة الصلاحيّات على مستوى مؤشّر الترابط والعودة الآمنة
حول رموز انتحال الهوية في Windows، نستعرض طريقة التفكير اللازمة للتعامل الآمن معها في العمل الفعليّ، بدءاً من رمز الوصول ورمز العمليّة ال...
ما ينبغي فعله قبل التخلّص من حاسوب Windows ── قائمة تحقّق عمليّة لمحو البيانات وفكّ ربط الحسابات والنسخ الاحتياطيّ
نستعرض ما ينبغي فعله قبل التخلّص من حاسوب Windows أو تسليمه أو بيعه أو إعادته بموجب عقد إيجار، من زاوية النسخ الاحتياطيّ، ومحو البيانات، ...
التعهيد الخارجي والتطوير التعاقدي لتطبيقات Windows: ما ينبغي تنظيمه قبل الطلب
قبل تكليف جهة خارجية بتطوير تطبيق Windows تعاقديّاً، إليك النقاط التي ينبغي تنظيمها: تعديل البرمجيّات القائمة، وتكامل الأجهزة، وCOM/Activ...
لماذا أصبح Windows على هذا الشكل الآن: تطوّر إصدارات Windows عبر التاريخ من منظور المطوِّر
نرتّب التغيّرات من Windows 95 إلى Windows 11، لا كجدول زمنيّ للمظهر، بل من منظور مطوِّر تطبيقات Windows: التوافق، والاستقرار، وإدارة الصل...
معالجة الأخطاء وتصميم إعادة التنفيذ في PowerShell ── من فخّ عدم نجاعة try/catch إلى قواعد exit code وإعادة المحاولة
نرتّب من منظور عمليّ الفرق بين الأخطاء المُنهِية وغير المُنهِية في PowerShell، والفخّ الذي يجعل try/catch غير فعّال والقاعدة الثابتة لـ -...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- ما هو MFC؟
- MFC اختصار لـ Microsoft Foundation Classes، وهو إطار عمل لتطبيقات Windows يجعل التعامل مع Win32 API أسهل من خلال تمثيله كفئات C++. تُمثَّل النافذة بالفئة CWnd، ومربّع الحوار بالفئة CDialog، والتطبيق ككلّ بالفئة CWinApp، وما شابه. إنه ليس «مكتبة سحريّة يمكن الكتابة بها دون معرفة Windows»، بل هو تنظيم لآليّات Windows ضمن أنواع C++ وإطار عمل، لذا فمعرفة رسائل Windows والمقابض (Handles) وغيرها من أساسيّات Win32 لا تزال ضروريّة.
- هل MFC لا يزال قابلاً للاستخدام اليوم؟ هل الدعم مستمرّ؟
- لا يزال MFC متاحاً حتى الآن في Visual Studio، ولا يزال الدعم مستمرّاً له. غير أنّ وثائق Microsoft تشير إلى أنّه لن تُضاف ميزات جديدة ولن تُحدَّث الوثائق. من حيث الوضع، فإنّ صيانة تطبيقات MFC القائمة وإضافة ميزات إليها وتحديث بيئة البناء (Build) والانتقال التدريجيّ إلى واجهة مستخدم أخرى أمور شائعة عمليّاً، في حين ينبغي التفكير بحذر قبل اعتماده في تطبيق واجهة رسوميّة عامّ جديد كليّاً. يتطلّب الأمر تثبيت المكوّن الفرديّ الخاصّ به من Visual Studio Installer (C++ MFC for latest build tools).
- ما هي خريطة الرسائل (Message Map) في MFC؟
- هي آليّة في MFC تربط رسائل Windows والأوامر بدوالّ المعالجة (Handlers). بين BEGIN_MESSAGE_MAP وEND_MESSAGE_MAP، تُكتَب المطابقة مثل «عند النقر على هذا الزرّ، استدعِ هذه الدالّة» عبر وحدات ماكرو مثل ON_BN_CLICKED وON_COMMAND. إن بحثتَ عن دالّة ولم تجد استدعاءً مباشراً لها رغم أنّها تُنفَّذ عند وقوع حدث، فتحقّق من خريطة الرسائل. في مراجعة شيفرة MFC، من المهمّ التحقّق من خريطة الرسائل إلى جانب دوالّ المعالجة، لا من دوالّ المعالجة وحدها.
- كيف يمكن الانتقال من MFC إلى تقنيّة أخرى؟
- استهداف إعادة كتابة شاملة دفعة واحدة يسهل أن يفشل. الترتيب الواقعيّ هو: إعادة إنتاج بيئة البناء، تثبيت السلوك القائم عبر بيانات اختبار، فصل المنطق (Logic) عن معالِجات أحداث واجهة المستخدم ونقله إلى مكتبة C++ غير مرتبطة بـ MFC، إضافة اختبارات آليّة، توثيق المواصفات الخارجيّة، ثمّ الاستبدال التدريجيّ بدءاً من الشاشات اللازمة. جعل الهدف «إخراج المنطق المهمّ المحتجَز داخل MFC» بدل جعل الهدف «التخلّص من MFC» بحدّ ذاته يجعل النجاح أسهل.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.
روابط عامة