Apa itu MFC di Windows ── pengetahuan dasar untuk memelihara aset yang sudah ada

· Diperbarui pada: · · Windows, MFC, VisualC++, Cpp, Win32, NativeApp, DesktopApp, LegacyCode, Pemanfaatan aset yang sudah ada

Riwayat revisi (2 pembaruan, terakhir pada 1 Sep 2026)

Catatan perubahan yang dilakukan pada artikel ini. Jika versi sebelumnya telah diarsipkan, versi itu tetap dapat dibaca melalui tautan permanen dengan DOI.

Perbaikan tinjauan Codex: label tautan Microsoft Learn «MFC と ATL» diganti menjadi «MFC dan ATL».
Diterjemahkan ulang sebagai terjemahan lengkap dari naskah Jepang. Versi bahasa Indonesia sebelumnya adalah ringkasan yang hanya memindahkan sebagian naskah, sehingga bagian, tabel, gambar Mermaid, keterangan gambar, dan FAQ tidak ada. Semuanya dipulihkan sesuai naskah Jepang, dan klaim teknisnya sama dengan versi Jepang.
Publikasi pertama
Mengutip artikel ini(DOI (arsip terdaftar): 10.5281/zenodo.22173978)

DOI di bawah mengarah ke versi yang telah diarsipkan sebelumnya dan mungkin berbeda dari teks saat ini. Gunakan URL halaman ini untuk merujuk teks saat ini.

Go Komura (2026). Apa itu MFC di Windows ── pengetahuan dasar untuk memelihara aset yang sudah ada. KomuraSoft LLC. https://comcomponent.com/id/blog/2026/06/11/000-windows-mfc-overview/

DOI (arsip terdaftar)
10.5281/zenodo.22173978
DOI (versi terakhir yang didaftarkan)
10.5281/zenodo.22173979

1. Hal pertama yang perlu dipahami

Ketika memelihara aplikasi desktop Windows yang lama, nama-nama seperti ini kadang muncul.

CWinApp
CWnd
CDialog
CDialogEx
CFrameWnd
CDocument
CView
CString
CFile
CArchive
BEGIN_MESSAGE_MAP
ON_COMMAND
ON_BN_CLICKED
DoDataExchange
UpdateData

Itulah nama yang sering muncul di MFC, framework aplikasi Windows untuk C++. MFC adalah singkatan dari Microsoft Foundation Classes, pustaka yang membuat Win32 API lebih mudah ditangani sebagai kelas C++.

Dalam pengembangan aplikasi Windows saat ini ada banyak pilihan — WinUI, WPF, Windows Forms, Electron, Qt, teknologi web, dan lainnya — sehingga MFC jarang menjadi kandidat pertama untuk pengembangan baru. Namun ini bukan teknologi yang sudah hilang. Pada aplikasi bisnis, instrumen pengukuran, perangkat lunak kendali, CAD/CAM, alat internal, dan perangkat lunak paket yang sudah lama dipakai, basis kode MFC masih sering harus dipelihara.

Berikut sudut pandang yang sebaiknya dipegang lebih dulu ketika memahami MFC.

MFC adalah peta penting untuk membaca aplikasi desktop Windows yang lama
MFC tidak menyembunyikan Win32 API, melainkan membungkusnya secara C++
Tanpa mengenal konvensi MFC, perilaku mudah salah dibaca, lebih dari yang disarankan tampilan kodenya
Nilainya lebih besar pada pemeliharaan, perpanjangan umur, dan migrasi bertahap aset yang sudah ada, bukan pada adopsi baru

Artikel ini menguraikan gambaran MFC, struktur aplikasi, message map, Document/View, dialog, DDX/DDV, resource, build, dan hal yang perlu diperhatikan saat pemeliharaan.

Pembaca yang dibayangkan adalah orang yang bisa menulis C++, tetapi belum berpengalaman dengan Win32 API maupun MFC. Tingkat pengetahuan awal sebatas ini sudah cukup.

Bidang Tingkat yang diharapkan
C++ Dapat membaca kelas, pewarisan, fungsi virtual, pointer, dan referensi
Win32 API Boleh belum berpengalaman. Cukup pernah mendengar kata window dan message
Visual Studio Pernah membuka solusi dan membangunnya
COM / OLE Boleh belum berpengalaman. Cukup dicari ketika diperlukan di bab 27

Di akhir bab 2 ada daftar “pengetahuan yang diperlukan untuk membaca MFC”, tetapi tidak perlu mengumpulkan semuanya lebih dulu. Cukup membaca sambil kembali ke daftar itu pada saat dibutuhkan.

Urutan membaca juga tidak harus dari bab 1 sampai bab 41. Berikut jalur terpendek menurut tujuan.

Tujuan Bab yang dibaca
Hanya ingin tahu apa itu MFC dan di mana posisinya sekarang Bab 2–4, bab 36
Ingin bisa membaca kode MFC yang sudah ada Bab 6 → 9 → 10 → 14 → 12 dan 13 → 38
Ingin memperbaiki build yang gagal Bab 5, 25, 32, 33
Ingin menyelidiki bug Bab 34 → 35 → 8 dan 22
Ingin memutuskan kebijakan pemeliharaan atau migrasi Bab 30, 31, 37, 39

Yang paling perlu dikuasai lebih dulu adalah dua hal: message map (bab 9) dan Document/View (bab 14). Setelah keduanya dipahami, dapat dibaca “mengapa fungsi yang pemanggilnya tidak ditemukan justru dijalankan” dan “bagaimana data terhubung dengan layar”. Sebaliknya, jika dua bab ini dilewati, penjelasan bab-bab lain menjadi menggantung.

Cuplikan kode yang muncul di artikel ini dipublikasikan di GitHub sebagai kumpulan kode rujukan yang diorganisasikan per bab ke dalam berkas.

windows-mfc-overview - komurasoft-blog-samples (GitHub)

Pada diagram, garis utuh menunjukkan relasi yang selalu berlaku dan garis putus-putus menunjukkan relasi bersyarat (syaratnya ada pada penjelasan masing-masing relasi di halaman rincian). Daftar lengkap relasi (total 20, beserta bukti dan tingkat kepastian) serta definisi konsep utama dikumpulkan di halaman rincian peta pengetahuan (dalam bahasa Jepang). Data: JSON-LD / Turtle

2. Apa itu MFC

MFC adalah pustaka kelas untuk membuat aplikasi desktop native Windows dalam C++.

Jika Win32 API dipakai langsung, kode yang khas kira-kira seperti ini.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_PAINT:
        // pemrosesan drawing
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Win32 API sangat kuat, tetapi karena disusun di sekitar fungsi C, handle, pesan, dan callback, aplikasi yang besar cepat menjadi sulit dilacak. MFC membuat ini dapat ditangani sebagai kelas C++: jendela adalah CWnd, dialog adalah CDialog, aplikasi secara keseluruhan adalah CWinApp, jendela frame adalah CFrameWnd, dan view adalah CView.

class CMainFrame : public CFrameWnd
{
public:
    CMainFrame();

protected:
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    DECLARE_MESSAGE_MAP()
};

MFC tidak mengganti Win32 API dengan sesuatu yang sama sekali berbeda. Ini framework C++ yang tipis tetapi cakupannya luas, dibangun di atas cara berpikir Win32 API. Karena itu, untuk membaca MFC dibutuhkan bukan hanya kelas MFC, melainkan juga pengetahuan di sekitar ini.

Pesan Windows
Handle seperti HWND
GDI/GDI+
Berkas resource
COM/OLE
DLL dan runtime
Encoding
Thread dan message loop

Wujud sebenarnya MFC lebih dekat ke “mekanisme Windows yang ditata dengan tipe dan framework C++” daripada “pustaka ajaib yang memungkinkan menulis tanpa mengenal Windows”.

3. Apakah MFC masih bisa dipakai sekarang

MFC masih dapat dipakai di Visual Studio saat ini. Namun salah paham tentang posisinya berbahaya. MFC memang masih didukung, tetapi ini bukan framework UI terbaru yang aktif mendapat fitur baru. Di dokumentasi MFC milik Microsoft pun ada catatan bahwa MFC tetap didukung, sementara penambahan fitur baru dan pembaruan dokumentasi tidak dilakukan.

Karena itu, posisi MFC kira-kira seperti ini.

Pemeliharaan aplikasi MFC yang sudah ada              -> dalam praktik sering terjadi
Penambahan fitur pada aplikasi MFC yang sudah ada     -> mungkin
Pembaruan lingkungan build aplikasi MFC               -> penting
Migrasi bertahap dari MFC ke UI lain                  -> mungkin
Adopsi pada aplikasi GUI umum yang benar-benar baru   -> putuskan dengan hati-hati

Khususnya pada aplikasi bisnis yang sudah lama dipakai, UI, pencetakan, I/O berkas, kendali perangkat, protokol proprietary, dan integrasi COM kadang terkumpul di dalam MFC.

Pada basis kode seperti itu, yang diperlukan lebih dulu bukan “membuang MFC”, melainkan “membuat MFC dapat dibaca”.

4. Area yang menjadi keunggulan MFC

Area khas tempat MFC dipakai adalah aplikasi desktop native Windows.

Secara konkret, aplikasi seperti ini.

Alat bisnis yang berpusat pada dialog
Aplikasi SDI yang membuka dan menyunting berkas
Aplikasi MDI yang menangani beberapa dokumen
Layar kendali instrumen pengukuran atau peralatan manufaktur
Aplikasi native CAD/CAM
Aplikasi yang banyak memakai cetak dan pratinjau
Aplikasi yang mencakup integrasi ActiveX atau OLE
Aplikasi yang erat dengan Windows API lama atau aset COM

Kekuatan MFC adalah beroperasi dekat dengan bagian native Windows. Jendela, menu, toolbar, status bar, dialog, common control, pencetakan, file dialog, Registry, drawing GDI, dan sejenisnya dapat ditangani sebagai kelas C++.

Sebaliknya, kelemahan MFC adalah penyusunan UI modern, data binding, kemudahan pengujian, pemrosesan asinkron, layout modern, dukungan DPI tinggi, internasionalisasi, dan aksesibilitas tidak dapat ditulis sealamiah framework yang lebih baru.

Jika ciri-cirinya disusun, kira-kira seperti ini.

Dekat dengan native Windows
Dapat dikendalikan secara langsung dari C++
Aset yang sudah ada banyak
Pengetahuan Win32 diperlukan
Banyak konvensi lama
Struktur yang mudah diuji harus dirapikan sendiri

5. Persiapan memakai MFC di Visual Studio

Meski C++ sudah terpasang di Visual Studio, MFC tidak otomatis ikut. MFC diperlakukan sebagai komponen individual Visual Studio Installer. Secara khas, periksa komponen-komponen di sekitar ini.

Desktop development with 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
Apakah diperlukan MFC versi Spectre Mitigations

Jika berkas terkait MFC tidak ditemukan saat build, periksa bukan hanya pengaturan proyek, melainkan juga apakah komponen MFC sudah terpasang di sisi Visual Studio.

Cara memilihnya di installer adalah sebagai berikut.

1. Buka "Visual Studio Installer" dari menu Start
2. Pada instalasi Visual Studio yang dituju, tekan "Modify"
3. Di tab "Workloads", centang "Desktop development with C++"
4. Di "Installation details" di sisi kanan layar yang sama,
   centang "C++ MFC for latest v143 build tools"
5. Jika tidak ditemukan, pindah ke tab "Individual components",
   ketik MFC di kotak pencarian, lalu cari
6. Tekan "Modify" untuk menginstal

Hanya memilih workload “Desktop development with C++” kadang tidak memasang MFC, jadi pastikan komponen itu sendiri tercentang pada langkah 4 atau langkah 5.

Jika dipasang dari skrip atau CI, tentukan component ID.

Nama tampilan Component ID
C++ MFC for latest v143 build tools Microsoft.VisualStudio.Component.VC.ATLMFC
C++ ATL for latest v143 build tools Microsoft.VisualStudio.Component.VC.ATL
Desktop development with C++ (workload Build Tools) Microsoft.VisualStudio.Workload.VCTools

Ada tidaknya MFC dapat dicek dari keberadaan folder atlmfc tempat header MFC diletakkan. Header dan pustaka MFC masuk di bawah toolset MSVC.

<lokasi instalasi Visual Studio>\VC\Tools\MSVC\<versi toolset>\atlmfc\include\afxwin.h

Jika mencari dengan PowerShell, lakukan seperti ini.

Get-ChildItem -Path "$env:ProgramFiles\Microsoft Visual Studio\2022" -Recurse -Filter afxwin.h -ErrorAction SilentlyContinue |
    Select-Object -ExpandProperty FullName

Jika tidak ada keluaran sama sekali, komponen MFC belum terpasang.

Jika dibangun dalam keadaan itu, proses berhenti pada tahap menyertakan afxwin.h. Secara khas, error kompilasi berikut muncul.

fatal error C1083: Cannot open include file: 'afxwin.h': No such file or directory

Membaca error ini sebagai “salah pengaturan include path” lalu mengacak properti proyek adalah jalan memutar yang sering terjadi. Ketika afxwin.h atau afxdialogex.h tidak ditemukan, curigai dulu sisi Visual Studio Installer.

Hal yang sama berlaku di lingkungan CI atau build server. Jika build berhasil di Visual Studio lokal tetapi gagal di CI, penyebabnya kadang ada tidaknya komponen MFC, atau perbedaan versi toolset target.

6. Struktur dasar aplikasi MFC

Aplikasi MFC kira-kira memiliki struktur seperti ini.

Kelas turunan CWinApp
  Bertanggung jawab atas inisialisasi dan penutupan seluruh aplikasi

Kelas turunan CFrameWnd / CMDIFrameWnd / CDialog
  Bertanggung jawab atas jendela utama atau dialog

Kelas turunan CView
  Bertanggung jawab atas tampilan layar dan interaksi pengguna

Kelas turunan CDocument
  Bertanggung jawab atas data dan penyimpanan berkas

Berkas resource
  Menyimpan menu, dialog, ikon, string, dan sejenisnya

Message map
  Mengikat pesan Windows dan perintah ke fungsi handler

Misalnya, pada aplikasi MFC yang sederhana, kelas turunan CWinApp seperti ini muncul.

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 adalah kelas yang mewakili aplikasi secara keseluruhan. Pada aplikasi MFC, biasanya ada satu objek yang diturunkan dari CWinApp.

Objek global seperti theApp ini mungkin terasa aneh pada awalnya, tetapi di MFC inilah struktur yang standar.

7. Apa yang dilakukan CWinApp

CWinApp penting sebagai pintu masuk aplikasi MFC. Pada aplikasi Win32 biasa, WinMain, registrasi window class, message loop, dan sebagainya ditulis sendiri, tetapi di MFC sebagian besar itu diambil alih framework. Pengembang terutama meng-override InitInstance dan menulis inisialisasi yang khas untuk aplikasi.

BOOL CMyApp::InitInstance()
{
    CWinApp::InitInstance();

    // membaca pengaturan
    // inisialisasi COM
    // membuat jendela utama
    // mendaftarkan document template

    return TRUE;
}

Pemrosesan yang mudah masuk ke InitInstance kira-kira seperti ini.

Inisialisasi common control
Pengaturan kunci Registry
Pembacaan daftar berkas yang baru dipakai
Pendaftaran document template
Pembuatan main frame
Pemrosesan argumen baris perintah
Inisialisasi COM/OLE

Saat pemeliharaan, memeriksa kelas turunan CWinApp lebih dulu membuat urutan start-up seluruh aplikasi lebih mudah terlihat.

8. CWnd adalah kelas di pusat MFC

Banyak kelas UI MFC berfondasikan CWnd, yang mewakili jendela Windows. Namun objek CWnd dan HWND bukan hal yang sama.

HWND
  Handle jendela yang dikelola OS Windows

CWnd
  Objek pembungkus C++ agar HWND lebih mudah ditangani

Di MFC, CWnd menyimpan HWND di dalamnya.

HWND hWnd = m_hWnd;

Atau diperoleh seperti ini.

HWND hWnd = GetSafeHwnd();

Yang penting saat pemeliharaan: meskipun CWnd* ada, HWND yang bersesuaian mungkin sudah dihancurkan.

Karena itu, apakah jendela masih valid dicek kira-kira seperti ini.

if (pWnd != nullptr && ::IsWindow(pWnd->GetSafeHwnd()))
{
    pWnd->ShowWindow(SW_SHOW);
}

Dalam investigasi bug MFC, penting untuk melihat apakah lifetime objek C++ CWnd dan lifetime handle jendela Windows yang sebenarnya tidak bergeser.

9. Apa itu message map

Salah satu mekanisme yang paling khas MFC adalah message map.

Aplikasi Windows menerima klik mouse, input keyboard, penggambaran ulang, perubahan ukuran jendela, pemilihan menu, dan sejenisnya sebagai pesan Windows.

Pada Win32 API, pesan biasanya diproses dengan pernyataan switch di WndProc.

Di MFC, itu diikat ke fungsi handler lewat message map.

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"));
}

Arti kode ini adalah sebagai berikut.

Jika tombol IDC_BUTTON_OK diklik
panggil CMyDialog::OnClickedButtonOk

Jika alur dari datangnya pesan sampai dipanggilnya handler digambarkan, kira-kira seperti ini.

AdaTidak adaDitemukanTidak ada sampai akhirPesan WindowsWM_COMMAND / WM_PAINT, dan sebagainyaMessage loopdijalankan oleh framework MFCCWnd::WindowProcpintu masuk bersama yang disediakan MFCApakah ada entri yang cocokdi message map kelas sendiri?Memanggil fungsi handleryang ditunjuk oleh ON_BN_CLICKED dan sejenisnyaMencari message map kelas dasarmenelusuri CDialogEx, lalu CDialog, lalu CWndApakah ditemukan di suatu tempat?DefWindowProcdiserahkan ke pemrosesan bawaan Windows

Pernyataan switch tidak terlihat karena framework mengambil alih bagian “menelusuri message map dari kelas sendiri ke kelas dasar” ini. DECLARE_MESSAGE_MAP dan BEGIN_MESSAGE_MAP lebih mudah dipahami jika dianggap sebagai makro yang menyiapkan tabel korespondensi per kelas untuk pencarian itu.

Jika belum terbiasa membaca MFC, dari mana suatu fungsi dipanggil terasa sulit dilacak. Jika pencarian tidak menemukan pemanggilan langsung, lihat message map.

Fungsi tidak dipanggil secara langsung
tetapi dijalankan saat event terjadi
-> periksa makro BEGIN_MESSAGE_MAP / ON_...

Dalam tinjauan kode MFC, penting untuk memeriksa message map bersama dengan fungsi handler, bukan handler-nya saja.

10. Command routing

Di MFC, operasi pada menu dan toolbar juga diperlakukan sebagai perintah (command).

Yang representatif adalah ON_COMMAND.

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)
END_MESSAGE_MAP()

void CMainFrame::OnFileOpen()
{
    // pemrosesan membuka berkas
}

MFC punya mekanisme yang mengantarkan perintah ke objek yang tepat.

Misalnya, bahkan untuk ID_EDIT_COPY yang sama, siapa yang memprosesnya dapat berubah: view yang sedang aktif, dokumen, frame, atau aplikasi.

View yang aktif
Dokumen
Jendela frame
Aplikasi

Dalam urutan seperti itu, perintah diteruskan ke objek yang dapat memprosesnya.

Karena itu, di MFC “fungsi mana yang dipanggil ketika menu ditekan” kadang sulit dilacak hanya dengan pencarian string sederhana.

Poin yang dilihat saat pemeliharaan kira-kira di sekitar ini.

Apa command ID-nya
ON_COMMAND ada di kelas mana
ON_UPDATE_COMMAND_UI ada di mana
View mana yang sedang aktif
Apakah struktur Document/View dipakai

11. Apa itu ON_UPDATE_COMMAND_UI

Di MFC, ON_UPDATE_COMMAND_UI kadang dipakai untuk memperbarui enabled/disabled, keadaan check, dan teks tampilan pada item menu atau tombol toolbar.

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());
}

Dengan ini, menu atau tombol hanya diaktifkan ketika item memang dapat dihapus.

Ketika memakai aplikasi MFC dan menyelidiki perilaku seperti tombol yang entah mengapa greyed out, menu yang tidak dapat ditekan, atau keadaan check yang berubah, mencari ON_UPDATE_COMMAND_UI kadang menyingkap penyebabnya.

12. Aplikasi MFC berbasis dialog

Salah satu bentuk MFC yang paling mudah dipahami adalah aplikasi berbasis dialog.

Pada layar pengaturan, alat bisnis sederhana, layar operasi perangkat, dan sejenisnya, susunannya kadang berpusat pada dialog.

Secara khas, kelas diturunkan dari CDialog atau 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;
};

Pada kode berbasis dialog, unsur-unsur berikut sering muncul.

IDD_...        ID resource dialog
IDC_...        ID control
OnInitDialog   pemrosesan inisialisasi
DoDataExchange asosiasi control dan variabel anggota
UpdateData     sinkronisasi layar dan variabel
ON_BN_CLICKED  pemrosesan klik tombol

Dialog tidak hanya soal tampilan: resource, variabel anggota, message map, dan pemrosesan inisialisasi bekerja secara bersamaan.

13. DDX dan DDV

Yang sering muncul pada dialog MFC adalah DDX dan DDV.

DDX = Dialog Data Exchange
DDV = Dialog Data Validation

DDX adalah mekanisme yang mengaitkan control di dialog dengan variabel anggota C++, dan DDV adalah mekanisme yang memvalidasi nilai input itu.

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);
}

Memanggil UpdateData(TRUE) menyalin nilai yang diinput di layar ke variabel anggota.

void CSettingsDialog::OnBnClickedOk()
{
    if (!UpdateData(TRUE))
    {
        return;
    }

    // di sini m_name dan m_interval sudah berisi nilai yang diinput di layar
    SaveSettings(m_name, m_interval);

    CDialogEx::OnOK();
}

Sebaliknya, memanggil UpdateData(FALSE) menyalin nilai variabel anggota ke layar.

BOOL CSettingsDialog::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    m_name = _T("default");
    m_interval = 60;
    UpdateData(FALSE);

    return TRUE;
}

Ketika nilai input pada dialog MFC terasa aneh, periksa sekitar ini.

Apakah DDX didefinisikan di DoDataExchange
Apakah UpdateData(TRUE) dipanggil
Apakah waktu pemanggilan UpdateData(FALSE) benar
Apakah DDV menolak input
Apakah ID control cocok dengan resource

14. Arsitektur Document/View

Salah satu ciri besar MFC adalah arsitektur Document/View.

Ini struktur untuk memisahkan data yang ditangani aplikasi dari tampilannya.

CDocument
  Menyimpan data
  Bertanggung jawab atas baca-tulis berkas
  Memberitahu pembaruan ke beberapa view

CView
  Menampilkan data
  Menangani interaksi pengguna
  Mengelola drawing dan keadaan seleksi

Jika hubungan frame, view, dan dokumen digambarkan, kira-kira seperti ini.

diambil dengan GetDocumentmemberitahu lewat UpdateAllViewsTurunan CWinAppstart-up dan penutupan seluruh appDocument templateCSingleDocTemplate / CMultiDocTemplateJendela frameCFrameWnd / CMDIChildWndTurunan CDocumentdata dan I/O berkasTurunan CViewdrawing dan interaksi pengguna

Poin pentingnya: yang mengikat ketiga hal — frame, dokumen, dan view — adalah document template. Karena template ini didaftarkan di dalam InitInstance, jika ingin tahu “view mana yang melihat dokumen mana”, bacalah InitInstance lebih dulu.

Perbedaan arah juga perlu dipegang: GetDocument dipakai ketika view melihat dokumen, dan UpdateAllViews dipakai ketika perubahan di sisi dokumen diteruskan ke semua view. Memahami itu memudahkan menelusuri bug ketika layar tidak diperbarui.

Misalnya, pada editor teks, editor bentuk, alat penyuntingan berkas pengaturan, atau aplikasi seperti CAD, memisahkan data dari tampilan punya makna.

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;
};

Di sisi view, dokumen diambil lalu digambar.

void CMyView::OnDraw(CDC* pDC)
{
    CMyDocument* pDoc = GetDocument();
    if (pDoc == nullptr)
    {
        return;
    }

    for (const auto& item : pDoc->m_items)
    {
        // drawing memakai pDC
    }
}

Keuntungan Document/View adalah data yang sama mudah ditampilkan di beberapa view.

Misalnya, data yang sama dapat ditampilkan seperti ini.

View berbentuk tabel
View grafik
View rinci
Pratinjau
View cetak

Namun, memakai Document/View pada layar pengaturan sederhana atau alat kecil kadang justru terasa membuat strukturnya berat.

Saat pemeliharaan, menentukan lebih dulu apakah aplikasi itu memakai Document/View atau berpusat pada dialog membuat kode lebih mudah dilacak.

15. SDI dan MDI

Di MFC, digabungkan dengan Document/View, struktur SDI atau MDI sering muncul.

SDI = Single Document Interface
MDI = Multiple Document Interface

SDI pada dasarnya adalah bentuk yang menangani satu dokumen dalam satu frame.

Jendela utama
  Satu dokumen
  Satu atau beberapa view

MDI memiliki beberapa jendela anak di dalam satu jendela induk, dan masing-masing menangani sebuah dokumen.

Frame induk MDI
  Frame anak MDI 1 -> dokumen 1
  Frame anak MDI 2 -> dokumen 2
  Frame anak MDI 3 -> dokumen 3

Pada aplikasi Windows lama, MDI sering dipakai.

Saat pemeliharaan, melihat nama kelas sudah memberi dugaan tentang strukturnya.

CFrameWnd          frame untuk SDI
CMDIFrameWnd       frame induk MDI
CMDIChildWnd       frame anak MDI
CSingleDocTemplate document template untuk SDI
CMultiDocTemplate  document template untuk MDI

Pada aplikasi yang dibuat wizard MFC, pemrosesan pendaftaran CSingleDocTemplate atau CMultiDocTemplate sering ada di dalam InitInstance.

16. Memahami berkas resource

Pada aplikasi MFC, berkas .rc sangat penting.

.rc adalah berkas resource Windows.

Di sini didefinisikan hal-hal seperti berikut.

Template dialog
Menu
Accelerator key
Ikon
Bitmap
String table
Informasi versi
Toolbar

Selain itu, ID resource didefinisikan di resource.h.

#define IDD_SETTINGS_DIALOG  101
#define IDC_EDIT_NAME        1001
#define IDC_EDIT_INTERVAL    1002
#define ID_FILE_OPEN         32771

Kode MFC memakai ID ini untuk mengikat resource dengan kode C++.

DDX_Text(pDX, IDC_EDIT_NAME, m_name);
ON_COMMAND(ID_FILE_OPEN, &CMainFrame::OnFileOpen)

Masalah yang sering terjadi saat pemeliharaan adalah ketidakcocokan ID resource.

ID di resource.h berubah
ID bentrok karena merge cabang lain
ID control di dialog tidak cocok dengan ID DDX
ID menu yang seharusnya sudah dihapus masih tersisa
ID di string table terduplikasi

Ketika menyelidiki perilaku aplikasi MFC, perlu melihat bukan hanya kode C++, melainkan juga .rc dan resource.h secara bersamaan.

17. Class Wizard dan kode tulis tangan

MFC punya sejarah yang terikat erat dengan Class Wizard di Visual Studio.

Memakai Class Wizard memungkinkan pembuatan otomatis message handler, variabel DDX, override fungsi virtual, dan sejenisnya.

Karena itu, di kode MFC banyak tersisa kode berbentuk hasil generate alat.

//{{AFX_DATA(CSettingsDialog)
//}}AFX_DATA

//{{AFX_MSG(CSettingsDialog)
//}}AFX_MSG

Pada Visual Studio yang lebih baru, tampilan dan bentuk yang dihasilkan kadang sudah berubah, tetapi pada basis kode lama penanda komentar seperti ini masih bisa tersisa.

Yang penting saat pemeliharaan adalah tidak merusak secara kasar batas antara kode hasil generate dan kode tulis tangan.

Jangan menghapus message map
Jangan merusak korespondensi DDX
Jangan mengubah ID resource tanpa alasan
Jangan menghapus begitu saja komentar yang mengandaikan Class Wizard lama

Di MFC, lolos kompilasi sebagai C++ saja tidak cukup. Bentuk yang diharapkan resource editor Visual Studio dan Class Wizard juga perlu, sampai tingkat tertentu, dipertahankan.

18. CString dan string

Kelas string yang sering muncul di MFC adalah CString.

CString name = _T("Komura");
CString message;
message.Format(_T("Hello, %s"), name.GetString());

CString adalah kelas string berpanjang variabel yang sering dipakai di kode keluarga MFC/ATL. Di C++ modern, std::string atau std::wstring lebih sering dipakai, tetapi di MFC, CString banyak dipakai karena kecocokannya dengan API dan control.

Saat pemeliharaan, encoding perlu disadari.

CString      setara CStringA atau CStringW, tergantung pengaturan proyek
CStringA     keluarga ANSI / MBCS
CStringW     keluarga Unicode / UTF-16
LPCTSTR      pointer string berbasis TCHAR
LPCSTR       keluarga char
LPCWSTR      keluarga wchar_t
std::string  biasanya keluarga char
std::wstring keluarga wchar_t

Pada aplikasi Windows saat ini, pada dasarnya lebih aman mengandaikan Unicode, tetapi pada aplikasi MFC lama, kode yang mengandaikan MBCS kadang masih tersisa.

CString text = _T("日本語");
std::wstring ws(text.GetString());

Bug konversi string sering muncul dalam pemeliharaan aplikasi MFC.

Kasus yang terutama perlu diwaspadai adalah seperti ini.

Membaca berkas yang mengandaikan Shift_JIS
Beralih ke build Unicode
DLL eksternal menuntut char*
COM menuntut BSTR
Konversi ceroboh ke std::string membuat teks rusak

Ketika melihat CString, jangan menganggapnya semata “kelas string lama”. Periksa bersama pengaturan character set proyek, API eksternal, dan format berkas.

19. CFile dan CArchive

MFC juga punya kelas untuk operasi berkas dan serialisasi. Yang representatif adalah CFile dan CArchive.

CFile file;
if (file.Open(path, CFile::modeRead))
{
    CArchive ar(&file, CArchive::load);
    // membaca dari ar
}

CArchive sering dipakai dalam mekanisme serialisasi MFC.

Pada kelas turunan CDocument, Serialize kadang di-override, dan pembacaan serta penyimpanan ditulis dalam fungsi yang sama.

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);
        }
    }
}

Serialisasi MFC nyaman, tetapi pada operasi jangka panjang perlu kehati-hatian.

Kompatibilitas dengan format berkas lama
Pengelolaan nomor versi
Pemulihan saat pembacaan gagal
Penanganan exception
Encoding
Endian
Apakah struct disimpan apa adanya

Pada aplikasi MFC yang lama memakai format biner proprietary, Serialize kadang secara de facto menjadi spesifikasi berkas.

Dalam kasus itu, sebelum mengubah kode, sebaiknya selalu siapkan data uji yang membaca berkas yang sudah ada.

20. Penggambaran GDI dan CDC

Pada penggambaran layar MFC, kelas CDC yang menangani Device Context Windows sering dipakai. Di CView::OnDraw, CDC* diteruskan sebagai argumen.

void CMyView::OnDraw(CDC* pDC)
{
    pDC->TextOut(10, 10, _T("Hello MFC"));
    pDC->Rectangle(10, 40, 200, 120);
}

Jika memakai pen atau brush, perhatikan pemilihan dan pemulihannya.

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);
}

Di sekitar objek GDI, kesalahan seperti ini menjadi masalah.

Setelah SelectObject, tidak dikembalikan ke semula
Objek GDI dibuat dalam jumlah besar lalu tidak dihancurkan
Peran OnPaint dan OnDraw dicampuradukkan
Tidak memakai double buffering sehingga layar berkedip
Pada DPI tinggi, drawing yang mengandaikan piksel tetap menjadi kacau

Pada bug penggambaran MFC, perlu memeriksa bukan hanya logika C++, melainkan juga resource GDI Windows, waktu penggambaran ulang, DPI, dan ukuran font.

21. Dialog modal dan dialog modeless

Di MFC, cara menampilkan dialog juga perlu diperhatikan.

Dialog modal ditampilkan dengan DoModal.

CSettingsDialog dlg(this);
if (dlg.DoModal() == IDOK)
{
    // pemrosesan saat OK
}

Dalam kasus ini, pemanggil menunggu sampai dialog ditutup.

Sebaliknya, pada dialog modeless, pemrosesan pemanggil kembali setelah dialog dibuat.

m_pToolDialog = new CToolDialog(this);
m_pToolDialog->Create(IDD_TOOL_DIALOG, this);
m_pToolDialog->ShowWindow(SW_SHOW);

Pada dialog modeless, pengelolaan lifetime penting.

Kapan dialog hasil new di-delete
Apakah jendela induk dihancurkan lebih dulu
Apakah PostNcDestroy dipakai di sisi dialog
Apakah tidak dibuat dua kali
Apakah pointer setelah ditutup masih tersisa

Dalam investigasi crash MFC, masalah lifetime dialog modeless kadang menjadi penyebabnya.

22. Lifetime objek C++ dan handle Windows

Yang sangat penting di MFC adalah perbedaan lifetime antara objek C++ dan handle Windows. Misalnya CWnd adalah objek C++, tetapi jendela yang sebenarnya dikelola Windows sebagai HWND, dan keduanya tidak selalu dibuat serta dihapus pada saat yang sama.

Objek CWnd ada tetapi HWND belum ada
HWND sudah dihancurkan tetapi objek CWnd masih ada
Pembungkus CWnd sementara sedang dibuat
Handle diganti dengan Attach/Detach

Misalnya, kode seperti ini perlu diwaspadai.

CWnd* pWnd = GetDlgItem(IDC_SOME_CONTROL);
// menyimpan pWnd ke anggota lalu dipakai belakangan

Jika pointer yang diperoleh dari GetDlgItem dipegang dalam jangka panjang, ada risiko merujuknya setelah jendela dihancurkan.

Jika perlu, kadang lebih aman memanggil GetDlgItem setiap kali, atau mengelola variabel anggota untuk control lewat DDX.

DDX_Control(pDX, IDC_LIST_ITEMS, m_listItems);

Di MFC, pointer yang bukan null belum tentu aman.

if (m_pDialog != nullptr && ::IsWindow(m_pDialog->GetSafeHwnd()))
{
    m_pDialog->SetWindowText(_T("Running"));
}

Intuisi ini sangat penting dalam pemeliharaan MFC.

23. Thread dan pembaruan UI

UI Windows pada dasarnya harus dioperasikan di UI thread tempat UI itu dibuat, dan pada aplikasi MFC pun sama. Mengoperasikan control UI secara langsung dari worker thread menjadi penyebab perilaku tidak stabil atau crash.

Contoh yang sebaiknya dihindari.

UINT WorkerThreadProc(LPVOID pParam)
{
    CMyDialog* pDlg = static_cast<CMyDialog*>(pParam);

    // hindari menyentuh UI secara langsung dari worker thread
    pDlg->SetDlgItemText(IDC_STATUS, _T("Done"));

    return 0;
}

Secara umum, UI thread diberitahu lewat PostMessage dan sejenisnya.

constexpr UINT WM_APP_WORK_DONE = WM_APP + 1;

UINT WorkerThreadProc(LPVOID pParam)
{
    HWND hWnd = static_cast<HWND>(pParam);

    // pemrosesan berat

    ::PostMessage(hWnd, WM_APP_WORK_DONE, 0, 0);
    return 0;
}

Di sisi UI, pesan itu diterima lewat message map.

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;
}

Di sekitar thread MFC, periksa hal berikut.

Apakah UI tidak disentuh langsung dari worker thread
Apakah PostMessage tidak dikirim setelah jendela dihancurkan
Apakah UI thread tidak dihentikan sambil menunggu thread selesai
Apakah penguncian data bersama sudah tepat
Apakah nilai kembali dan lifetime AfxBeginThread tidak disalahpahami

24. MFC DLL dan module state

Jika membuat DLL dengan MFC, muncul konsep module state.

Pada situasi seperti memuat resource dari MFC DLL, menampilkan dialog, atau membuat extension DLL, masalahnya adalah resource modul mana yang akan dicari.

Di pintu masuk fungsi MFC DLL, makro seperti ini kadang terlihat.

AFX_MANAGE_STATE(AfxGetStaticModuleState());

Ini agar MFC dapat memakai module state yang benar.

Jika makro ini dilupakan, masalah seperti berikut terjadi.

Resource dialog di dalam DLL tidak ditemukan
Resource string dibaca dari modul lain
Ikon atau menu tidak ditemukan
Hanya di debug yang berjalan, di release rusak

Saat memelihara MFC DLL, penting merapikan hubungan EXE, DLL biasa, extension DLL, dan resource DLL.

Perhatian terutama diperlukan jika MFC DLL dipanggil dari aplikasi non-MFC, atau jika susunannya berupa plugin.

25. Static linking MFC atau memakai shared DLL

Pada aplikasi MFC, di pengaturan proyek ada “Use of MFC”.

Dua pilihan yang representatif adalah sebagai berikut.

Use MFC in a Shared DLL
Use MFC in a Static Library

Jika memakai shared DLL, lingkungan eksekusi memerlukan runtime MFC dan Visual C++ runtime yang sesuai.

Jika static linking, paket distribusi kadang terlihat lebih sederhana, tetapi perlu mempertimbangkan ukuran berkas eksekusi, pembaruan, penerapan perbaikan keamanan, lisensi, dan syarat redistribusi.

Tidak ada yang selalu benar.

Bahan pertimbangan kira-kira di sekitar ini.

Apakah Visual C++ Redistributable dapat dipasang di tujuan distribusi
Apakah aplikasi ingin didekatkan ke satu exe
Bagaimana perbaikan keamanan diterapkan
Apakah beberapa aplikasi berbagi runtime yang sama
Apakah installer dapat disiapkan
Apa versi Windows sasarannya

Saat pemeliharaan, yang penting adalah memeriksa pengaturan saat ini lebih dulu.

Configuration Properties
  General
    Use of MFC

Selain itu, lihat juga pengaturan Runtime Library.

/MD   Multi-threaded DLL
/MDd  Multi-threaded Debug DLL
/MT   Multi-threaded
/MTd  Multi-threaded Debug

Jika pengaturan linking MFC dan CRT tercampur, di batas pustaka kadang muncul masalah alokasi/pelepasan memori.

26. Unicode, MBCS, dan TCHAR

Pada kode MFC lama, makro TCHAR, LPCTSTR, dan _T() sering muncul.

CString title = _T("Pengaturan");
SetWindowText(title);

Ini cara menulis agar mendukung baik build Unicode maupun build MBCS.

Build Unicode
  TCHAR    -> wchar_t
  LPCTSTR  -> const wchar_t*
  _T("...") -> L"..."

Build MBCS
  TCHAR    -> char
  LPCTSTR  -> const char*
  _T("...") -> "..."

Saat ini build Unicode sudah umum, tetapi pada aplikasi lama, pemrosesan yang mengandaikan MBCS kadang masih tersisa.

Khususnya pada berkas eksternal, protokol komunikasi, DLL lama, koneksi database, komunikasi serial, dan sejenisnya, asumsi encoding perlu diperiksa.

Yang perlu diwaspadai: jangan menganggap migrasi ke Unicode sebagai pekerjaan penggantian sederhana.

Ukuran array char: jumlah byte atau jumlah karakter
Apakah strlen dipakai
Apakah sizeof(buffer) dipakai sebagai jumlah karakter
API eksternal menerima UTF-16 atau Shift_JIS
Apakah format penyimpanan berkas boleh berubah

Ketika memperbaiki string di MFC, periksa bukan hanya tampilan layar, melainkan juga kompatibilitas berkas dan integrasi eksternal.

27. MFC dan COM/OLE/ActiveX

MFC juga dipakai pada aplikasi yang erat dengan COM, OLE, dan ActiveX.

Pada aplikasi bisnis lama, unsur seperti berikut kadang masih tersisa.

OLE Automation
ActiveX Control
COM server
COM client
IDispatch
BSTR
VARIANT
COleDispatchDriver
COleVariant

Pada pemrosesan start-up aplikasi MFC, kode seperti ini kadang muncul.

if (!AfxOleInit())
{
    AfxMessageBox(_T("OLE initialization failed"));
    return FALSE;
}

Jika COM/OLE dipakai, yang tampak seperti masalah MFC kadang sebenarnya disebabkan oleh inisialisasi COM, thread model, reference count, informasi registrasi, atau perbedaan 32bit/64bit.

Yang terutama perlu diwaspadai adalah ActiveX 32bit dan komponen COM.

Dari aplikasi MFC 32bit, pakai COM 32bit
Dari aplikasi MFC 64bit, pakai COM 64bit
Registrasi COM 32bit/64bit terpisah
ActiveX lama kadang tidak mendukung 64bit

Ketika meng-x64-kan aplikasi MFC, pastikan memeriksa bukan hanya kode UI, melainkan juga ketergantungan COM/OLE.

28. Dukungan DPI tinggi dan Windows masa kini

Jika aplikasi MFC lama dijalankan di Windows masa kini, tampilan kadang kacau di lingkungan DPI tinggi.

Misalnya, masalah seperti ini.

Teks terpotong
Tombol terlalu kecil
Drawing piksel tetap bergeser
Kacau jika rasio perbesaran berbeda antar monitor
Bitmap lama menjadi buram
Layout dialog menjadi sesak

Pada aplikasi desktop Windows, aplikasi perlu menyatakan mode dukungan DPI secara eksplisit.

Pada aplikasi MFC pun, manifest, resource, kode drawing, font, dan layout perlu diperiksa.

Khususnya, kode yang menuliskan koordinat tetap seperti ini mudah bermasalah pada DPI tinggi.

pDC->TextOut(10, 10, _T("Status"));
pDC->Rectangle(10, 40, 200, 80);

Koordinat yang mengandaikan piksel tetap membuat tampilan kacau jika DPI berubah.

Sudut pandang pemeriksaan saat pemeliharaan adalah sebagai berikut.

Pengaturan DPI pada application manifest
Font resource dialog
Drawing piksel tetap
Resolusi resource gambar
Perilaku di beberapa monitor
Tampilan di Windows 10 / Windows 11

Dukungan DPI tinggi pada MFC tidak selalu selesai hanya dengan mengubah pengaturan proyek. Pada UI lama, pemeriksaan layar yang sebenarnya dan perbaikan layout menjadi diperlukan.

29. Penanganan exception dan penanganan error

MFC punya kelas exception sendiri dan konvensi penanganan error.

Pada kode lama, makro seperti ini terlihat.

TRY
{
    // pemrosesan
}
CATCH(CFileException, e)
{
    e->ReportError();
}
END_CATCH

Ada juga kode yang tercampur dengan try / catch C++ modern.

try
{
    DoSomething();
}
catch (const std::exception& ex)
{
    // keluaran log
}

Yang perlu diwaspadai dalam pemeliharaan MFC adalah poin berikut.

Apakah exception MFC dan exception standar C++ tidak tercampur
Apakah lifetime objek exception tidak disalahpahami
Apakah makro THROW/CATCH lama dipahami
Apakah error nilai kembali dan exception tidak tercampur
Apakah keadaan tidak berakhir hanya dengan AfxMessageBox tanpa log

Pada aplikasi bisnis, penting bukan hanya menampilkan pesan error di layar, melainkan juga menyisakan log, riwayat operasi, nilai input, dan keadaan koneksi eksternal.

Pada aplikasi MFC lama, error kadang berakhir hanya dengan AfxMessageBox.

AfxMessageBox(_T("Penyimpanan gagal"));

Jika ingin menaikkan kemudahan pemeliharaan, tampilan UI dan pencatatan log sebaiknya dipisah.

LogError(_T("Save failed"), path);
AfxMessageBox(_T("Penyimpanan gagal. Periksa log."));

30. Bagaimana MFC dan C++ modern dapat hidup berdampingan

Hanya karena memelihara aplikasi MFC, tidak perlu menyesuaikan semuanya dengan cara menulis C++ lama.

Lapisan UI menghormati konvensi MFC, sementara logika domain dan pemrosesan perhitungan dapat dirapikan dengan C++ modern.

Misalnya, dipisah seperti ini.

Lapisan MFC
  CDialog
  CView
  CDocument
  CString
  Message map
  Operasi resource

Lapisan non-MFC
  std::string / std::wstring
  std::vector
  std::optional
  std::variant
  std::filesystem
  Kelas yang dapat diuji secara unit
  Logika bisnis

Bentuk yang buruk adalah semua pemrosesan ditumpuk di kelas dialog.

void CMainDialog::OnBnClickedExecute()
{
    // mengambil input
    // membaca berkas
    // komunikasi
    // perhitungan
    // pembaruan DB
    // pembaruan layar
    // keluaran log
    // penanganan exception
}

Kode seperti ini sulit diubah, sulit diuji, dan investigasi bug pun menjadi sulit.

Jika ingin memperbaiki, keluarkan logika dari kelas 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);
}

Dengan cara ini, m_service.Execute dapat diuji tanpa MFC.

Perbaikan yang paling efektif saat memelihara aset MFC yang sudah ada adalah memisahkan logika sedikit demi sedikit dari kelas UI.

31. Membuat kode MFC yang mudah diuji

Aplikasi MFC, apa adanya, sering sulit diuji secara unit.

Alasannya: UI, Win32, berkas, komunikasi, database, dan keadaan global mudah menjadi tightly coupled.

Cara berpikir agar lebih mudah diuji adalah sebagai berikut.

Jangan mencoba menguji CDialog atau CView secara langsung
Keluarkan dulu logika non-UI
Konversi tipe MFC di batas
Jadikan berkas dan komunikasi sebagai interface
Buat handler event layar menjadi tipis

Misalnya, pindahkan pemrosesan ke kelas C++ murni.

class PriceCalculator
{
public:
    int CalculateTotal(const std::vector<int>& prices) const
    {
        int total = 0;
        for (int price : prices)
        {
            total += price;
        }
        return total;
    }
};

Sisi MFC hanya bertanggung jawab atas input dan output.

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);
}

Dengan struktur ini, PriceCalculator dan ParsePrices dapat diuji dengan framework pengujian C++ biasa.

Tidak perlu membangun ulang seluruh aplikasi MFC sekaligus. Hanya mengeluarkan pemrosesan yang dapat diuji dari dalam handler event pun sudah ada efeknya.

32. Menetapkan lingkungan build

Dalam pemeliharaan aplikasi MFC, menetapkan lingkungan build itu penting.

Pada basis kode lama, hasil build kadang berubah karena perbedaan berikut.

Versi Visual Studio
Versi toolset MSVC
Versi Windows SDK
Ada tidaknya komponen MFC/ATL
x86 / x64 / ARM64
Debug / Release
Unicode / MBCS
Static linking MFC / shared DLL
Pengaturan runtime library
Precompiled header

Pada aplikasi MFC, banyak ketergantungan kadang terkumpul di stdafx.h atau pch.h.

#include "framework.h"
#include "MyApp.h"

Build kadang rusak karena hanya satu berkas yang pengaturan kompilasinya berbeda, atau pengaturan precompiled header bergeser.

Pada proyek pemeliharaan, menuliskan informasi ini di README membuat langkah berikutnya lebih mudah.

Versi Visual Studio yang diperlukan
Workload dan komponen individual yang diperlukan
Windows SDK yang diperlukan
Platform target
Cara linking MFC
Langkah build
Cara menjalankan CI
Cara membuat paket distribusi

Lepas dari ketergantungan “di PC saya bisa di-build” adalah langkah pertama pemeliharaan MFC.

33. Membangun MFC di CI

Aplikasi MFC pun dapat di-build di CI.

Namun, komponen MFC perlu terpasang di lingkungan CI.

Jika memakai Visual Studio Build Tools, component ID MFC/ATL perlu ditentukan lalu diinstal. ID yang ditentukan sama dengan tabel di bab 5.

Poin yang ingin diperiksa adalah sebagai berikut.

Apakah MFC sudah masuk di Build Tools
Apakah toolset sasaran cocok dengan proyek
Apakah Windows SDK terpasang
Apakah build x86 dan x64 keduanya diperiksa
Apakah resource compiler berjalan
Apakah ada pemrosesan tanda tangan
Apakah pembuatan installer juga menjadi sasaran CI

Pada aplikasi MFC, mengotomatiskan sampai uji UI tidak mudah, tetapi setidaknya otomatisasi di sekitar ini efektif.

Build Debug / Release
Build x86 / x64
Analisis statis
Uji unit
Pembuatan installer
Penyimpanan hash artefak
Pemeriksaan DLL ketergantungan

Dalam pemeliharaan jangka panjang, hanya menjaga keadaan yang dapat di-build pun sudah bernilai besar.

34. Tempat yang dilihat saat men-debug aplikasi MFC

Ketika menelusuri bug pada aplikasi MFC, urutan ini efisien.

1. Layar mana
2. Dialog, View, atau Frame
3. Apa resource ID yang sesuai dengan operasi
4. Fungsi mana yang dituju di message map
5. Apakah arah UpdateData benar
6. Jika Document/View, apa keadaan Document
7. Apakah command routing mengalir ke kelas lain
8. Apakah UI tidak disentuh dari worker thread
9. Apakah exception atau error tidak ditelan hanya dengan AfxMessageBox
10. Apakah tidak ada masalah inisialisasi atau lifetime yang khas build release

Misalnya, untuk bug “tombol ditekan tetapi tidak terjadi apa-apa”, periksa di sini.

Apakah IDC tombol benar
Apakah ON_BN_CLICKED ada
Apakah signature handler benar
Apakah resource dialog bukan yang lain
Apakah tombol tidak dinonaktifkan
Apakah UpdateData tidak gagal di tengah pemrosesan
Apakah exception tidak ditelan

Jika “menu tidak dapat ditekan”, yang ini.

Apakah tidak dinonaktifkan oleh ON_UPDATE_COMMAND_UI
Apakah command ID tidak terduplikasi
Apakah view aktif sesuai harapan
Di Frame/View/Document/App mana handler-nya

Karena event di permukaan dan pemrosesan yang sebenarnya terhubung lewat makro dan routing, sampai terbiasa, menggambarkan jalur pemanggilan memudahkan pemahaman. Diagram di bab 9 menjadi bentuk dasar jalur pemanggilan itu.

Jika ingin menaksir dari gejala, lihat dulu tabel di bab 35. Jebakan yang sering terjadi dan tempat memeriksa masing-masing sudah dipasangkan di situ.

35. Jebakan yang sering terjadi

Berikut jebakan yang sering terjadi dalam pemeliharaan MFC.

Mencari hanya pemanggilan fungsi tanpa melihat message map
Mengira CWnd* yang bukan null berarti valid
Mencampuradukkan lifetime HWND dan lifetime objek C++
Salah arah UpdateData(TRUE/FALSE)
Tidak menyadari dinonaktifkan oleh ON_UPDATE_COMMAND_UI
Tidak menyadari tabrakan ID di resource.h
Menyentuh UI secara langsung dari worker thread
Teks rusak saat konversi CString dan std::string
Merusak kode yang mengandaikan MBCS karena migrasi ke Unicode
Melupakan AFX_MANAGE_STATE di MFC DLL
Merusak COM/ActiveX yang mengandaikan x86 karena migrasi ke x64
Salah memilih/melepaskan objek GDI
Layout koordinat tetap kacau pada DPI tinggi

Untuk masing-masing, dipasangkan “gejala yang muncul” dan “di mana memeriksanya”. Pintu masuk debug sesuai dengan langkah di bab 34.

Jebakan Gejala yang mudah muncul Tempat memeriksa
Mencari hanya pemanggilan fungsi tanpa melihat message map Dijalankan padahal pemanggil tidak ditemukan Cari BEGIN_MESSAGE_MAP. Bab 9, langkah 4 di bab 34
Mengira CWnd* yang bukan null berarti valid Kadang crash, crash jika mengoperasikan layar yang sudah ditutup Periksa dengan ::IsWindow(pWnd->GetSafeHwnd()). Bab 8
Mencampuradukkan lifetime HWND dan lifetime objek C++ Access violation setelah dialog ditutup Apakah nilai kembali GetDlgItem tidak dipegang. Bab 22
Salah arah UpdateData Nilai input tidak diterapkan, nilai awal tidak muncul di layar Posisi pemanggilan UpdateData(TRUE) dan UpdateData(FALSE). Bab 13, langkah 5 di bab 34
Tidak menyadari dinonaktifkan oleh ON_UPDATE_COMMAND_UI Tombol atau menu greyed out dan tidak dapat ditekan Handler ON_UPDATE_COMMAND_UI. Bab 11, “menu tidak dapat ditekan” di bab 34
Tidak menyadari tabrakan ID di resource.h Dialog atau item menu lain yang bereaksi Cocokkan resource.h dan .rc berdasarkan ID. Bab 16
Menyentuh UI secara langsung dari worker thread Crash tidak stabil yang sulit direproduksi, kadang hang Apakah UI disentuh di dalam fungsi yang diberikan ke AfxBeginThread. Bab 23
Teks rusak saat konversi CString dan std::string Hanya teks Jepang yang rusak, ujung terpotong Pengaturan character set proyek dan lokasi konversi. Bab 18
Merusak kode yang mengandaikan MBCS karena migrasi ke Unicode Pergeseran panjang buffer, berkas yang sudah ada tidak dapat dibaca Apakah sizeof tidak dipakai sebagai jumlah karakter. Bab 26
Melupakan AFX_MANAGE_STATE di MFC DLL Dialog atau resource string di dalam DLL tidak ditemukan Awal fungsi publik DLL. Bab 24
Merusak COM/ActiveX yang mengandaikan x86 karena migrasi ke x64 Hanya build 64bit yang gagal saat start-up atau tampilan layar Apakah registrasi COM tidak hanya di sisi 32bit. Bab 27
Salah memilih dan melepaskan objek GDI Drawing kacau jika dijalankan lama Apakah kolom “GDI objects” di Task Manager tidak terus bertambah. Bab 20
Layout koordinat tetap kacau pada DPI tinggi Teks terpotong hanya di lingkungan rasio 150% Pengaturan DPI di manifest dan drawing koordinat tetap. Bab 28

Bug MFC kadang tidak terlihat jika hanya melihat tata bahasa C++. Perlu melihat sampai pesan Windows, resource, handle, modul, dan pengaturan runtime.

36. Apakah MFC harus dipilih untuk pengembangan baru

Untuk pengembangan baru sepenuhnya, apakah memilih MFC sebaiknya diputuskan dengan hati-hati.

Jika ada alasan memilih MFC, kira-kira kasus seperti ini.

Perlu berintegrasi erat dengan kode MFC yang sudah ada
Ingin memakai ulang komponen atau layar MFC yang sudah ada
Perlu kendali yang sangat dekat dengan Win32/GDI/COM
Keterampilan pemeliharaan MFC di perusahaan sudah cukup
Sasaran terbatas pada desktop Windows
Menurut rencana migrasi jangka panjang, perlu menambah dulu di atas MFC

Sebaliknya, pada kasus berikut, pilihan lain sebaiknya dipertimbangkan.

Ingin membuat UI modern
Perlu layout fleksibel atau animasi
Integrasi web atau cloud menjadi pusat
Ingin mengutamakan kemudahan pengujian
Ingin memilih teknologi yang lebih mudah diikuti pengembang junior
Perlu dukungan lintas platform
Ingin mengutamakan aksesibilitas dan DPI tinggi sejak awal

MFC bukan teknologi “tidak ada gunanya dipelajari sekarang”, dan juga bukan teknologi “dipilih karena baru”. Ini teknologi untuk berhadapan dengan aset native Windows yang sudah ada.

37. Cara berpikir saat bermigrasi dari MFC

Jika ingin memindahkan aplikasi MFC ke teknologi lain, mengejar perombakan total sekaligus mudah berakhir gagal.

Pertama, uraikan aplikasi seperti ini.

UI
Logika bisnis
Format berkas
Pemrosesan komunikasi
Pemrosesan database
Kendali perangkat
Pencetakan
Integrasi COM/OLE
Pengelolaan pengaturan
Log

Di antaranya, yang paling bergantung pada MFC adalah UI.

Sebaliknya, logika bisnis dan pemrosesan berkas punya kemungkinan untuk dipisahkan.

Urutan migrasi yang realistis adalah sebagai berikut.

1. Mereproduksi lingkungan build
2. Menstabilkan perilaku yang ada dengan data uji
3. Memisahkan logika dari handler event UI
4. Menggeser ke pustaka C++ non-MFC
5. Menambahkan pengujian otomatis
6. Mendokumentasikan spesifikasi eksternal
7. Mengganti secara bertahap mulai dari layar yang diperlukan

Lebih mudah berhasil jika tujuannya bukan “berhenti memakai MFC” itu sendiri, melainkan “mengeluarkan logika penting yang terkurung di dalam MFC”.

38. Pintu masuk saat membaca kode MFC

Ketika pertama kali membaca proyek MFC yang sudah ada, mulai dari berkas di sekitar ini.

*.vcxproj
  Lihat toolset, pengaturan MFC, character set, pengaturan runtime

resource.h
  Lihat ID resource

*.rc
  Lihat dialog, menu, string, ikon

*App.cpp / *App.h
  Lihat kelas turunan CWinApp dan InitInstance

MainFrm.cpp / MainFrm.h
  Lihat main frame serta menu/toolbar

*Doc.cpp / *Doc.h
  Jika Document/View, lihat struktur data dan pemrosesan penyimpanan

*View.cpp / *View.h
  Lihat drawing dan interaksi pengguna

*Dlg.cpp / *Dlg.h
  Lihat dialog, DDX, pemrosesan tombol

Berikutnya, kata kunci pencarian yang sering dipakai.

BEGIN_MESSAGE_MAP
ON_COMMAND
ON_UPDATE_COMMAND_UI
ON_BN_CLICKED
DoDataExchange
UpdateData
OnInitDialog
OnDraw
Serialize
AfxMessageBox
AfxBeginThread
AFX_MANAGE_STATE

Mencari sekitar ini membuat gerakan aplikasi lebih mudah terlihat.

39. Kebijakan desain saat memelihara MFC

Jika aset MFC yang sudah ada akan dipelihara dalam jangka panjang, kebijakan seperti ini efektif.

Buat handler event UI menjadi tipis
Jangan terlalu banyak menumpahkan CString atau CWnd ke lapisan non-UI
Pindahkan logika bisnis ke kelas C++ biasa
Buat uji kompatibilitas format berkas
Perjelas perbedaan x86/x64
Tinjau perubahan ID resource
Periksa module state MFC DLL
Rapikan log
Tetapkan build di CI
Secara berkala periksa layar pada DPI tinggi dan Windows 11

Khususnya, penting untuk tidak terlalu banyak menumpuk pemrosesan ke CDialog atau CView.

Kelas layar MFC dikonsentrasikan pada input, tampilan, dan pengantaran event.

Mengambil nilai dari layar
Menyerahkannya ke service
Menerapkan hasil ke layar

Jika dapat dijaga pada tingkat ini, meskipun MFC, pemeliharaannya menjadi jauh lebih mudah.

40. Daftar periksa praktis

Daftar periksa ketika menangani aplikasi MFC.

Apakah versi Visual Studio jelas
Apakah komponen MFC/ATL terinstal
Apakah target x86/x64 jelas
Apakah pengaturan Unicode/MBCS dipahami
Apakah MFC static linking atau shared DLL
Apa Visual C++ Redistributable yang diperlukan
Apakah resource.h dan .rc masuk sasaran tinjauan
Apakah jalur event ditelusuri lewat message map
Apakah arah UpdateData benar
Apakah struktur Document/View dipakai
Apakah pengelolaan lifetime dialog modeless aman
Apakah UI tidak disentuh langsung dari worker thread
Apakah ada tempat di MFC DLL yang memerlukan AFX_MANAGE_STATE
Apakah layar diperiksa di lingkungan DPI tinggi
Apakah ketergantungan COM/ActiveX mendukung 32bit/64bit
Apakah log tersisa
Apakah logika non-UI dapat diuji

MFC terasa khas sampai terbiasa, tetapi setelah tempat yang harus dilihat diketahui, dapat dibaca dengan cukup teratur.

41. Ringkasan

MFC adalah framework bersejarah untuk membangun aplikasi desktop native Windows dalam C++. Sudah bukan arus utama pengembangan baru, tetapi pada pemeliharaan aset yang sudah ada, pembaruan lingkungan build, penambahan fitur, dan migrasi bertahap, MFC tetap teknologi yang penting.

Yang terutama penting ketika memahami MFC adalah poin berikut.

MFC membuat Win32 API lebih mudah ditangani dalam C++
CWinApp mengelola aplikasi secara keseluruhan
Lifetime CWnd dan HWND tidak sama
Message map mengikat event dengan fungsi
Document/View adalah mekanisme yang memisahkan data dan tampilan
DDX/DDV dipakai untuk sinkronisasi nilai dialog dan validasi
Berkas resource dan resource.h sangat penting
CString dan TCHAR dipahami bersama pengaturan encoding
Pada MFC DLL, perhatikan module state
Aplikasi MFC lama bernilai ditinjau ulang dari sudut DPI tinggi, x64, CI, dan pengujian

Ketika menangani MFC, yang penting bukan memutuskan “lama berarti buruk”, melainkan memahami strukturnya lebih dulu. Pada aplikasi MFC yang sudah ada, kadang tertumpuk pengetahuan bisnis bertahun-tahun, spesifikasi per pelanggan, integrasi perangkat, dan kompatibilitas berkas. Untuk menjaga nilai itu sambil sedikit demi sedikit membuatnya lebih mudah dipelihara, yang realistis adalah memahami konvensi MFC, lalu memisahkan logika non-UI, serta merapikan build dan pengujian.

Jika diringkas dalam satu kalimat, kira-kira seperti ini.

Fondasi untuk menangani mekanisme aplikasi native Windows lewat kelas dan framework C++.

Dengan sudut pandang itu, MFC bukan sekadar teknologi lama, melainkan petunjuk untuk membaca aset Windows yang sudah ada secara aman.

Referensi

Artikel terbaru dengan tag yang sama untuk mendalami topik-topik terdekat.

Halaman-halaman ini menempatkan topik dalam konteks layanan dan keputusan yang lebih luas.

Artikel ini berkaitan langsung dengan layanan berikut.

Pertanyaan yang sering diajukan

Pertanyaan yang sering muncul dalam konsultasi tentang topik artikel ini.

Apa itu MFC?
MFC adalah singkatan dari Microsoft Foundation Classes, framework aplikasi Windows yang membuat Win32 API lebih mudah ditangani sebagai kelas C++. Jendela diwakili oleh CWnd, dialog oleh CDialog, dan aplikasi secara keseluruhan oleh CWinApp. Ini bukan pustaka ajaib yang memungkinkan menulis tanpa mengenal Windows, melainkan penataan mekanisme Windows lewat tipe dan framework C++. Karena itu, pengetahuan Win32 seperti pesan Windows dan handle tetap diperlukan.
Apakah MFC masih bisa dipakai? Apakah dukungannya masih berlanjut?
MFC masih dapat dipakai di Visual Studio saat ini dan tetap didukung. Namun di dokumentasi Microsoft terdapat catatan bahwa penambahan fitur baru maupun pembaruan dokumentasi tidak dilakukan. Posisinya: pemeliharaan aplikasi MFC yang sudah ada, penambahan fitur, pembaruan lingkungan build, dan migrasi bertahap ke UI lain adalah hal yang dalam praktik sering terjadi, sementara untuk aplikasi GUI umum yang benar-benar baru, keputusan untuk mengadopsinya harus diambil dengan hati-hati. Komponen individual Visual Studio Installer (C++ MFC for latest build tools) perlu diinstal.
Apa itu message map di MFC?
Mekanisme MFC yang mengikat pesan Windows dan perintah ke fungsi handler. Di antara BEGIN_MESSAGE_MAP dan END_MESSAGE_MAP, korespondensi seperti "jika tombol ini diklik, panggil fungsi ini" ditulis dengan makro seperti ON_BN_CLICKED atau ON_COMMAND. Jika pencarian fungsi tidak menemukan pemanggilan langsung, tetapi fungsi itu dijalankan saat event terjadi, periksa message map. Dalam tinjauan kode MFC, penting untuk memeriksa fungsi handler bersama dengan message map, bukan handler-nya saja.
Bagaimana cara bermigrasi dari MFC ke teknologi lain?
Mengejar perombakan total sekaligus mudah berakhir gagal. Urutan yang realistis adalah mereproduksi lingkungan build, menstabilkan perilaku yang ada dengan data uji, memisahkan logika dari handler event UI dan menggesernya ke pustaka C++ non-MFC, menambahkan pengujian otomatis, mendokumentasikan spesifikasi eksternal, lalu mengganti layar yang diperlukan secara bertahap. Lebih mudah berhasil jika tujuannya bukan "berhenti memakai MFC" itu sendiri, melainkan "mengeluarkan logika penting yang terkurung di dalam MFC".

Profil penulis

Halaman perkenalan penulis artikel.

Go Komura

Direktur KomuraSoft LLC

Berspesialisasi dalam pengembangan perangkat lunak Windows, konsultasi teknis, dan investigasi bug, terutama pada proyek dengan sistem yang sudah ada dan bug yang sulit direproduksi.

Kembali ke blog