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 lama

1. Hal pertama yang perlu dipahami

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

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

Itulah nama-nama yang sering muncul di MFC, framework aplikasi Windows untuk C++. MFC adalah singkatan dari Microsoft Foundation Classes, pustaka yang memudahkan pemakaian Win32 API 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 merapikan 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 Belum berpengalaman pun tidak apa. Cukup pernah mendengar kata window dan message
Visual Studio Pernah membuka solusi dan membangunnya
COM / OLE Belum berpengalaman pun tidak apa. Cukup dicari ketika diperlukan di bab 27

Di akhir bab 2 ada daftar «pengetahuan yang diperlukan untuk membaca MFC», tetapi Anda 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 malfungsi 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, Anda dapat membaca «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)

Peta pengetahuan artikel ini

MFC adalah kerangka kerja yang membuat Win32 API lebih mudah ditangani sebagai kelas C++; CWinApp bertanggung jawab atas inisialisasi seluruh aplikasi, message map mengikat pesan Windows ke fungsi handler, arsitektur Document/View memisahkan data dan tampilan UI, dan DDX/DDV mencocokkan kontrol dialog dengan variabel anggota lalu memvalidasinya. Untuk memakai MFC, komponen MFC perlu diinstal secara terpisah lewat Visual Studio Installer, dan dapat dikonfigurasi apakah memakai shared DLL atau static linking. Artikel ini menunjukkan posisi bahwa pada aplikasi GUI umum yang benar-benar baru, adopsi MFC harus dipertimbangkan dengan hati-hati, sementara pada pemeliharaan, penambahan fitur, dan migrasi bertahap aplikasi MFC yang ada, secara realistis sering bernilai.

Peta pengetahuan pemeliharaan aplikasi desktop Windows dengan MFCDiagram yang menunjukkan bahwa MFC membungkus Win32 API dalam C++ dan mengimplementasikan mekanisme seperti message map dan Document/View, posisinya yang cocok untuk pemeliharaan aset yang ada tetapi harus dipertimbangkan dengan hati-hati untuk pengembangan GUI baru, serta hubungan titik yang perlu diperhatikan pada lingkungan build dan integrasi COMmenggunakanmenggunakanmengotomatiskanmengimplementasikanmensyaratkanmengimplementasikanmensyaratkanmenggunakanmengimplementasikanmensyaratkanmenggunakanmensyaratkandikonfigurasi denganmensyaratkanmenggunakanmensyaratkanmensyaratkantidak disarankandisarankan untukmensyaratkanMFC (Microsoft Foundation Classes)Win32 APICWinAppmessage maparsitektur Document/ViewDDX/DDVfile resource (.rc/resource.h)CStringkomponen MFC di Visual Studio Installercara linking MFC (Use of MFC)status modul MFCActiveXpersyaratan kecocokan bitnessdukungan DPI tinggipengembangan aplikasi GUI baru sepenuhnyapemeliharaan dan migrasi bertahap aplikasi MFC yang ada

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 gambar
        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 Anda membutuhkan 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 Anda 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, gambar 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 Anda rapikan 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 Anda membangun 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 Anda menulis sendiri WinMain, registrasi window class, message loop, dan sebagainya, 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.

Alur dari pesan Windows sampai handlerDiagram yang menunjukkan pesan Windows masuk ke message loop MFC, dicek di message map kelas sendiri, lalu ditelusuri ke kelas dasar, dan jika tidak ditemukan diserahkan ke DefWindowProc.YaTidakDitemukanTidak 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 Anda 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()
{
    // Proses 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 gambar dan keadaan seleksi

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

Hubungan frame, view, dan dokumenDiagram yang menunjukkan kelas turunan CWinApp mendaftarkan document template, yang mengikat jendela frame, CDocument, dan CView, serta arah GetDocument dan UpdateAllViews.diambil dengan GetDocumentmemberitahu lewat UpdateAllViewsTurunan CWinAppstart-up dan penutupan seluruh appDocument templateCSingleDocTemplate / CMultiDocTemplateJendela frameCFrameWnd / CMDIChildWndTurunan CDocumentdata dan I/O berkasTurunan CViewgambar dan interaksi pengguna

Poin pentingnya: yang mengikat ketiga hal — frame, dokumen, dan view — adalah document template. Karena template ini didaftarkan di dalam InitInstance, jika Anda 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 malfungsi ketika layar tidak diperbarui.

Misalnya, pada editor teks, editor gambar, 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)
    {
        // Gambar 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, Anda perlu melihat bukan hanya kode C++, melainkan juga .rc dan resource.h secara bersamaan.

17. Class Wizard dan kode tulisan 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 tulisan 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. Gambar GDI dan CDC

Pada gambar 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.

Tidak mengembalikan ke semula setelah SelectObject
Membuat banyak objek GDI tanpa menghancurkannya
Mencampur peran OnPaint dan OnDraw
Tidak memakai double buffering sehingga terjadi flicker
Gambar yang mengandaikan piksel tetap rusak pada DPI tinggi

Pada bug gambar MFC, yang perlu diperiksa 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 yang di-new di-delete
Apakah jendela induk dihancurkan lebih dulu
Apakah sisi dialog memakai PostNcDestroy
Apakah tidak dibuat dua kali
Apakah pointer setelah ditutup tidak tertinggal

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 objek C++ dan handle Windows. Misalnya CWnd adalah objek C++, tetapi jendela yang sebenarnya dikelola Windows sebagai HWND, dan keduanya tidak selalu dibuat bersamaan lalu hilang bersamaan.

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

Misalnya, kode seperti ini perlu diwaspadai.

CWnd* pWnd = GetDlgItem(IDC_SOME_CONTROL);
// Menyimpan pWnd ke anggota dan memakainya nanti

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 non-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 dari UI thread tempatnya dibuat, dan hal yang sama berlaku pada aplikasi MFC. 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, beritahu UI thread lewat PostMessage atau 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, terima 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 lock pada data bersama sudah tepat
Apakah nilai kembali dan lifetime AfxBeginThread tidak disalahpahami

24. MFC DLL dan module state

Ketika membuat DLL dengan MFC, muncul konsep module state.

Pada situasi seperti memuat resource dari MFC DLL, menampilkan dialog, atau membuat extension DLL, masalahnya menjadi: resource modul mana yang dilihat.

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, malfungsi seperti berikut terjadi.

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

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

Khususnya perlu hati-hati jika MFC DLL dipanggil dari aplikasi non-MFC, atau jika susunannya berupa plugin.

25. Menaut MFC secara statis atau memakai shared DLL

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

Dua pilihan yang representatif adalah ini.

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

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

Pada taut statis, artefak distribusi kadang terlihat sederhana, tetapi Anda 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 pembaruan keamanan diterapkan
Apakah beberapa aplikasi berbagi runtime yang sama
Apakah installer dapat disediakan
Apa versi Windows sasarannya

Saat pemeliharaan, yang penting adalah memastikan 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 taut MFC dan CRT tercampur, masalah alokasi/pembebasan memori di batas pustaka kadang muncul.

26. Unicode, MBCS, dan TCHAR

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

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

Ini cara penulisan untuk 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 yang umum, tetapi pada aplikasi lama, pemrosesan yang mengandaikan MBCS kadang masih tersisa.

Khususnya pada berkas eksternal, protokol komunikasi, DLL lama, koneksi basis data, komunikasi serial, dan sejenisnya, Anda perlu memastikan asumsi encoding.

Yang perlu diwaspadai: jangan menganggap Unicode-isasi sebagai pekerjaan substitusi 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?
Bolehkah format penyimpanan berkas berubah?

Ketika memperbaiki sisi string 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-unsur seperti berikut kadang 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 sebagai masalah MFC kadang sebenarnya disebabkan inisialisasi COM, threading model, reference count, informasi registrasi, atau perbedaan 32-bit/64-bit.

Yang terutama perlu diwaspadai adalah ActiveX 32-bit atau komponen COM.

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

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

28. Dukungan DPI tinggi dan Windows modern

Ketika aplikasi MFC lama dijalankan di Windows modern, tampilan kadang rusak di lingkungan DPI tinggi.

Misalnya, masalah seperti ini.

Teks terpotong
Tombol terlalu kecil
Gambar piksel tetap bergeser
Rusak jika rasio perbesaran berbeda antar monitor
Bitmap lama menjadi buram
Layout dialog menjadi rapat

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

Pada aplikasi MFC pun, Anda perlu memeriksa manifest, resource, kode gambar, font, dan layout.

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

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

Koordinat yang mengandaikan piksel tetap membuat tampilan rusak ketika DPI berubah.

Sudut pandang pemeriksaan saat pemeliharaan adalah seperti ini.

Pengaturan DPI pada application manifest
Font resource dialog
Gambar piksel tetap
Resolusi resource gambar
Perilaku pada 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 perlu.

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)
{
    // Menulis log
}

Dalam pemeliharaan MFC, yang perlu diwaspadai 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("Gagal menyimpan"));

Untuk menaikkan kemudahan pemeliharaan, lebih baik memisahkan tampilan UI dan pencatatan log.

LogError(_T("Save failed"), path);
AfxMessageBox(_T("Gagal menyimpan. Silakan periksa log."));

30. Bagaimana MFC dan C++ modern hidup berdampingan

Hanya karena Anda memelihara aplikasi MFC, tidak berarti semuanya harus disesuaikan dengan cara penulisan 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 di-unit-test
  Logika bisnis

Bentuk yang buruk adalah keadaan di mana semua pemrosesan disumpal ke kelas dialog.

void CMainDialog::OnBnClickedExecute()
{
    // Mengambil input
    // Membaca berkas
    // Komunikasi
    // Perhitungan
    // Pembaruan DB
    // Pembaruan layar
    // Penulisan log
    // Penanganan exception
}

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

Jika ingin memperbaikinya, 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 berdampak dalam memelihara aset MFC yang sudah ada adalah sedikit demi sedikit memisahkan logika dari kelas UI.

31. Membuat kode MFC mudah diuji

Aplikasi MFC, apa adanya, sering sulit di-unit-test.

Alasannya: UI, Win32, berkas, komunikasi, basis data, dan keadaan global mudah terikat rapat.

Cara berpikir agar lebih mudah diuji adalah seperti ini.

Jangan mencoba menguji CDialog atau CView secara langsung
Pisahkan dulu logika non-UI
Konversi tipe MFC di batas
Jadikan berkas dan komunikasi sebagai antarmuka
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 uji C++ biasa.

Anda tidak perlu membangun ulang seluruh aplikasi MFC sekaligus. Hanya mengeluarkan pemrosesan yang dapat diuji dari dalam event handler pun sudah berdampak.

32. Menetapkan lingkungan build

Dalam pemeliharaan aplikasi MFC, menetapkan lingkungan build 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
Taut statis 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 yang bergeser.

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

Versi Visual Studio yang diperlukan
Workload dan individual components yang diperlukan
Windows SDK yang diperlukan
Platform target
Cara taut MFC
Langkah build
Cara menjalankan CI
Cara membuat artefak distribusi

Lulus dari «di PC saya bisa di-build» adalah langkah pertama pemeliharaan MFC.

33. Membangun MFC di CI

Aplikasi MFC juga dapat di-build di CI.

Namun, komponen MFC perlu terpasang di lingkungan CI.

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

Berikut poin yang ingin diperiksa.

Apakah MFC terpasang di Build Tools
Apakah toolset sasaran cocok dengan proyek
Apakah Windows SDK terpasang
Apakah build x86 dan x64 keduanya dicek
Apakah resource compiler berjalan
Apakah ada pemrosesan penandatanganan
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
Unit test
Pembuatan installer
Penyimpanan hash artefak
Pemeriksaan DLL dependensi

Pada 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 malfungsi pada aplikasi MFC, urutan berikut efisien.

1. Layar yang 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 tidak mengalir ke kelas lain lewat command routing
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, jika malfungsinya «tidak terjadi apa-apa meski tombol ditekan», 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 dugaan
Handler ada di Frame/View/Document/App yang mana

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

Jika ingin mendapat petunjuk dari gejalanya, lihat dulu tabel di bab 35. Jebakan yang sering terjadi dan tempat memastikan masing-masing sudah dipasangkan di sana.

35. Jebakan yang sering terjadi

Berikut jebakan yang sering terjadi dalam pemeliharaan MFC.

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

Untuk masing-masing, dipasangkan «gejala seperti apa yang muncul» dan «di mana memastikan». Pintu masuk debug-nya sesuai dengan prosedur bab 34.

Jebakan Gejala yang mudah muncul Tempat memastikan
Mencari hanya pemanggilan fungsi tanpa melihat message map Dijalankan padahal pemanggil tidak ditemukan Cari BEGIN_MESSAGE_MAP. Bab 9, prosedur 4 di bab 34
Menganggap CWnd* non-null berarti valid Kadang crash, crash saat mengoperasikan layar yang sudah ditutup Lewatkan ::IsWindow(pWnd->GetSafeHwnd()). Bab 8
Mencampur 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 tercermin, nilai awal tidak muncul di layar Posisi pemanggilan UpdateData(TRUE) dan UpdateData(FALSE). Bab 13, prosedur 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 bentrok ID di resource.h Dialog atau item menu lain yang bereaksi Cocokkan resource.h dan .rc menurut ID. Bab 16
Menyentuh UI langsung dari worker thread Cara crash yang sulit direproduksi, kadang hang Apakah UI disentuh di dalam fungsi yang diberikan ke AfxBeginThread. Bab 23
Teks rusak karena 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 saat Unicode-isasi Pergeseran panjang buffer, berkas yang sudah ada tidak dapat dibaca Apakah sizeof tidak dipakai sebagai jumlah karakter. Bab 26
Lupa AFX_MANAGE_STATE pada MFC DLL Dialog atau resource string di dalam DLL tidak ditemukan Awal fungsi publik DLL. Bab 24
Merusak COM/ActiveX yang mengandaikan x86 saat x64-isasi Hanya build 64-bit yang gagal saat start-up atau tampilan layar Apakah registrasi COM tidak hanya di sisi 32-bit. Bab 27
Salah memilih dan membebaskan objek GDI Gambar rusak jika dijalankan lama Apakah kolom “GDI objects” di Task Manager terus bertambah. Bab 20
Layout koordinat tetap rusak pada DPI tinggi Teks terpotong hanya di lingkungan perbesaran 150% Pengaturan DPI di manifest dan gambar koordinat tetap. Bab 28

Malfungsi MFC kadang tidak dapat dipahami hanya dengan melihat tata bahasa C++; Anda perlu melihat sampai pesan Windows, resource, handle, modul, dan pengaturan runtime.

36. Haruskah MFC dipilih untuk pengembangan baru

Apakah memilih MFC untuk pengembangan yang benar-benar baru sebaiknya diputuskan dengan hati-hati.

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

Perlu berintegrasi rapat dengan kode MFC yang sudah ada
Ingin memakai ulang bagian atau layar MFC yang sudah ada
Perlu kendali yang sangat dekat dengan Win32/GDI/COM
Keterampilan pemeliharaan MFC di organisasi sudah memadai
Sasaran terbatas pada desktop Windows
Dalam rencana migrasi jangka panjang, perlu menambah dulu di MFC

Sebaliknya, pada kasus berikut lebih baik mempertimbangkan pilihan lain.

Ingin membuat UI modern
Perlu layout fleksibel atau animasi
Integrasi web atau cloud menjadi pusat
Ingin menekankan kemudahan pengujian
Ingin memilih teknologi yang mudah diikuti pengembang yang lebih junior
Perlu dukungan lintas platform
Ingin menekankan aksesibilitas atau 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 basis data
Kendali perangkat
Pencetakan
Integrasi COM/OLE
Pengelolaan pengaturan
Log

Dari semuanya, yang paling bergantung pada MFC adalah UI.

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

Urutan migrasi yang realistis adalah seperti ini.

1. Mereproduksi lingkungan build
2. Menstabilkan perilaku yang ada dengan data uji
3. Memisahkan logika dari handler event UI
4. Menggesernya ke pustaka C++ non-MFC
5. Menambahkan pengujian otomatis
6. Mendokumentasikan spesifikasi eksternal
7. 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».

38. Pintu masuk saat membaca kode MFC

Ketika pertama kali membaca proyek MFC yang sudah ada, mulailah 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 gambar 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 hal-hal 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 membiarkan CString atau CWnd bocor 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
Lakukan pemeriksaan layar secara berkala pada DPI tinggi dan Windows 11

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

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

Ambil nilai dari layar
Serahkan ke layanan
Cerminkan hasil ke layar

Jika dapat dijaga sampai tingkat ini, MFC pun menjadi cukup mudah dipelihara.

40. Daftar periksa praktis

Daftar periksa ketika menangani aplikasi MFC.

Apakah versi Visual Studio jelas
Apakah komponen MFC/ATL terpasang
Apakah target x86/x64 jelas
Apakah pengaturan Unicode/MBCS dipahami
Apakah MFC taut statis atau shared DLL
Apa Visual C++ Redistributable yang diperlukan
Apakah resource.h dan .rc menjadi sasaran tinjauan
Apakah jalur event dilacak 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 yang memerlukan AFX_MANAGE_STATE pada MFC DLL
Apakah layar dicek di lingkungan DPI tinggi
Apakah ketergantungan COM/ActiveX mendukung 32-bit/64-bit
Apakah log tersisa
Apakah logika non-UI dapat diuji

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

41. Ringkasan

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

Yang khususnya penting untuk 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 layak ditinjau ulang dari sudut DPI tinggi, x64, CI, dan pengujian

Ketika menangani MFC, yang penting bukan memutuskan «lama jadi buruk», melainkan memahami strukturnya lebih dulu. Pada aplikasi MFC yang sudah ada, pengetahuan bisnis bertahun-tahun, spesifikasi per pelanggan, integrasi perangkat, dan kompatibilitas berkas kadang terkumpul. Untuk menjaga nilai itu sambil sedikit demi sedikit membuatnya lebih mudah dipelihara, yang realistis adalah memahami konvensi MFC, lalu memisahkan logika non-UI, dan merapikan build serta 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 dengan 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, sebuah framework aplikasi Windows yang memudahkan pemakaian Win32 API sebagai kelas C++. Jendela diwakili oleh CWnd, dialog oleh CDialog, dan aplikasi secara keseluruhan oleh CWinApp. Ini bukan «pustaka ajaib yang memungkinkan Anda menulis tanpa mengenal Windows», melainkan penataan mekanisme Windows lewat tipe dan framework C++, sehingga 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 tidak ada penambahan fitur baru maupun pembaruan dokumentasi. 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. Anda perlu menginstal komponen individual Visual Studio Installer (C++ MFC for latest build tools).
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