WindowsのMFCとは何か ── 既存資産を保守するための基礎知識

· · Windows, MFC, VisualC++, Cpp, Win32, NativeApp, DesktopApp, LegacyCode, 既存資産活用

1. 最初に押さえるべきこと

Windows の古いデスクトップアプリケーションを保守していると、こんな名前に出会うことがあります。

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

これらは、MFC と呼ばれる C++ 向けの Windows アプリケーションフレームワークでよく登場するものです。MFC は Microsoft Foundation Classes の略で、Win32 API を C++ のクラスとして扱いやすくするためのライブラリです。

今の Windows アプリ開発では、WinUI、WPF、Windows Forms、Electron、Qt、Web 技術などさまざまな選択肢があるため、MFC を新規開発の第一候補として扱う場面は減っています。とはいえ、消えた技術でもありません。業務アプリケーション、計測機器、制御ソフト、CAD/CAM、社内ツール、古くからあるパッケージソフトなどでは、今でも MFC のコードベースを保守する場面があります。

MFC を理解するうえで持っておきたい視点を、先に挙げておきます。

MFCは古いWindowsデスクトップアプリを読むための重要な地図である
MFCはWin32 APIを隠すものではなく、C++らしく包んだものと考える
MFCの作法を知らないと、コードの見た目以上に挙動を読み間違える
新規採用よりも、既存資産の保守・延命・段階的移行で価値が出る

この記事では、MFC の概要、アプリケーション構造、メッセージマップ、Document/View、ダイアログ、DDX/DDV、リソース、ビルド、保守時の注意点を整理します。

なお、この記事に登場するコード断片は、章ごとにファイルへ整理した参照用コード集として GitHub で公開しています。

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

2. MFCとは何か

MFC は、Windows のネイティブデスクトップアプリケーションを C++ で作るためのクラスライブラリです。

Win32 API を直接使う場合、典型的にはこういうコードを書きます。

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

Win32 API は非常に強力ですが、C の関数、ハンドル、メッセージ、コールバックを中心に組み立てるため、大きなアプリケーションでは見通しが悪くなりやすいです。MFC はこれを C++ のクラスとして扱えるようにしたもので、ウィンドウは CWnd、ダイアログは CDialog、アプリケーション全体は CWinApp、フレームウィンドウは CFrameWnd、ビューは CView といったクラスで表現されます。

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

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

MFC は、Win32 API を完全に別物に置き換えるものではなく、Win32 API の考え方を土台にした、薄いけれども広範囲な C++ フレームワークです。そのため、MFC を読むには、MFC のクラスだけでなく、このあたりの知識も必要になります。

Windowsメッセージ
HWNDなどのハンドル
GDI/GDI+
リソースファイル
COM/OLE
DLLとランタイム
文字コード
スレッドとメッセージループ

「Windows を知らなくても書ける魔法のライブラリ」というより、「Windows の仕組みを C++ の型とフレームワークで整理したもの」というのが MFC の実像です。

3. MFCはいまでも使えるのか

MFC は、現在も Visual Studio で利用できます。ただし、位置づけの誤解は禁物です。今でもサポートされてはいるものの、活発に新機能が追加されていく最新 UI フレームワークではなく、Microsoft の MFC ドキュメントにも、MFC は引き続きサポートされる一方で、新機能追加やドキュメント更新は行われないという注記が見られます。

このため、MFC の位置づけはだいたいこうなります。

既存MFCアプリの保守              -> 現実的によくある
既存MFCアプリの機能追加          -> あり得る
MFCアプリのビルド環境更新        -> 重要
MFCから別UIへの段階的移行        -> あり得る
完全新規の一般的なGUIアプリで採用 -> 慎重に判断

特に、長く使われてきた業務アプリケーションでは、UI、印刷、ファイル入出力、デバイス制御、独自プロトコル、COM 連携などが MFC の中にまとまっていることがあります。

そのようなコードベースでは、「MFC を捨てる」よりも先に、「MFC を読める状態にする」ことが必要です。

4. MFCが得意だった領域

MFC が使われてきた代表的な領域は、Windows のネイティブデスクトップアプリケーションです。

具体的には、こういうアプリケーションです。

ダイアログ中心の業務ツール
ファイルを開いて編集するSDIアプリ
複数ドキュメントを扱うMDIアプリ
計測機器や製造装置の制御画面
CAD/CAM系のネイティブアプリ
印刷やプレビューを多用するアプリ
ActiveXやOLE連携を含むアプリ
古いWindows APIやCOM資産と密接なアプリ

MFC の強みは、Windows ネイティブの部品と近いところで動くことです。ウィンドウ、メニュー、ツールバー、ステータスバー、ダイアログ、コモンコントロール、印刷、ファイルダイアログ、レジストリ、GDI 描画などを、C++ のクラスとして扱えます。

一方で、MFC の弱みは、現代的な UI 構築、データバインディング、テスト容易性、非同期処理、モダンなレイアウト、高 DPI 対応、多言語化、アクセシビリティなどが、最近のフレームワークほど自然には書けないことです。

特徴を並べると、こうなります。

Windowsネイティブに近い
C++で直接的に制御できる
既存資産が多い
Win32の知識が必要
古い作法が多い
テストしやすい構造には自分で整える必要がある

5. Visual StudioでMFCを使う準備

Visual Studio で C++ をインストールしていても、MFC が必ず入っているとは限りません。MFC は Visual Studio Installer の個別コンポーネントとして扱われるためです。代表的には、このあたりのコンポーネントを確認します。

C++ によるデスクトップ開発
MSVC v143 - VS 2022 C++ x64/x86 build tools
Windows SDK
C++ MFC for latest v143 build tools
C++ ATL for latest v143 build tools
Spectre Mitigations版のMFCが必要かどうか

ビルド時に MFC 関連のファイルが見つからない場合、プロジェクト設定だけでなく、Visual Studio 側に MFC コンポーネントが入っているかを確認します。

CI 環境やビルドサーバーでも同じで、ローカルの Visual Studio ではビルドできるのに CI では失敗する場合、MFC コンポーネントの有無や、ターゲットツールセットのバージョン違いが原因になることがあります。

6. MFCアプリケーションの基本構造

MFC アプリケーションは、だいたい次のような構造を持ちます。

CWinApp派生クラス
  アプリケーション全体の初期化と終了を担当する

CFrameWnd / CMDIFrameWnd / CDialog派生クラス
  メインウィンドウやダイアログを担当する

CView派生クラス
  画面表示やユーザー操作を担当する

CDocument派生クラス
  データやファイル保存を担当する

リソースファイル
  メニュー、ダイアログ、アイコン、文字列などを保持する

メッセージマップ
  Windowsメッセージやコマンドをハンドラー関数に結びつける

たとえば、シンプルな MFC アプリでは、こんな CWinApp 派生クラスが出てきます。

class CMyApp : public CWinApp
{
public:
    virtual BOOL InitInstance();
};

CMyApp theApp;

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

    CMainFrame* pFrame = new CMainFrame;
    m_pMainWnd = pFrame;

    pFrame->Create(nullptr, _T("My MFC Application"));
    pFrame->ShowWindow(SW_SHOW);
    pFrame->UpdateWindow();

    return TRUE;
}

CWinApp はアプリケーション全体を表すクラスです。 MFC アプリケーションでは、通常 CWinApp から派生したオブジェクトが 1 つ存在します。

この theApp のようなグローバルオブジェクトは、最初こそ違和感があるかもしれませんが、MFC ではこれが標準的な構造です。

7. CWinAppは何をしているのか

CWinApp は、MFC アプリケーションの入口として重要です。通常の Win32 アプリケーションでは WinMain、ウィンドウクラス登録、メッセージループなどを自分で書きますが、MFC ではその多くをフレームワークが引き受けます。開発者は主に InitInstance をオーバーライドして、アプリケーション固有の初期化を書きます。

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

    // 設定の読み込み
    // COM初期化
    // メインウィンドウ作成
    // ドキュメントテンプレート登録

    return TRUE;
}

InitInstance に書かれやすいのは、こういった処理です。

共通コントロールの初期化
レジストリキーの設定
最近使ったファイルリストの読み込み
ドキュメントテンプレートの登録
メインフレームの生成
コマンドライン引数の処理
COM/OLEの初期化

保守時には、まず CWinApp 派生クラスを確認すると、アプリケーション全体の起動順序が見えやすくなります。

8. CWndはMFCの中心にあるクラス

MFC の UI クラスの多くは、Windows のウィンドウを表す CWnd を基底にしています。ただし、CWnd オブジェクトと HWND は同じものではありません。

HWND
  Windows OSが管理するウィンドウハンドル

CWnd
  HWNDを扱いやすくするためのC++ラッパーオブジェクト

MFC では、CWnd が内部に HWND を持ちます。

HWND hWnd = m_hWnd;

あるいは、次のように取得します。

HWND hWnd = GetSafeHwnd();

保守時に重要なのは、CWnd* が存在していても、対応する HWND がすでに破棄されている可能性があることです。

そのため、ウィンドウが有効かどうかは、こんな形で確認します。

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

MFC のバグ調査では、CWnd の C++ オブジェクト寿命と、実際の Windows ウィンドウハンドルの寿命がずれていないかを見ることが大事です。

9. メッセージマップとは何か

MFC らしさが最も出る仕組みの 1 つが、メッセージマップです。

Windows アプリケーションは、マウスクリック、キー入力、再描画、ウィンドウサイズ変更、メニュー選択などを、Windows メッセージとして受け取ります。

Win32 API では、通常 WndProcswitch 文でメッセージを処理します。

MFC では、それをメッセージマップでハンドラー関数に結びつけます。

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_BN_CLICKED(IDC_BUTTON_OK, &CMyDialog::OnClickedButtonOk)
    ON_WM_CLOSE()
END_MESSAGE_MAP()

void CMyDialog::OnClickedButtonOk()
{
    AfxMessageBox(_T("Clicked"));
}

このコードの意味はこうです。

IDC_BUTTON_OKというボタンがクリックされたら
CMyDialog::OnClickedButtonOkを呼び出す

MFC を読み慣れていないと、関数がどこから呼ばれているのか分かりにくく感じます。検索しても直接呼び出しが見つからない場合は、メッセージマップを見ます。

関数が直接呼ばれていない
でもイベント時に実行されている
-> BEGIN_MESSAGE_MAP / ON_... マクロを確認する

MFC のコードレビューでは、ハンドラー関数だけを見るのではなく、メッセージマップとセットで確認することが重要です。

10. コマンドルーティング

MFC では、メニューやツールバーの操作もコマンドとして扱われます。

代表的なのは ON_COMMAND です。

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

void CMainFrame::OnFileOpen()
{
    // ファイルを開く処理
}

MFC には、コマンドを適切なオブジェクトへ配送する仕組みがあります。

たとえば、同じ ID_EDIT_COPY でも、現在アクティブなビュー、ドキュメント、フレーム、アプリケーションのどれが処理するかが変わることがあります。

アクティブビュー
ドキュメント
フレームウィンドウ
アプリケーション

このような順序で、処理できるオブジェクトにコマンドが渡っていきます。

そのため、MFC では「メニューを押したらどの関数が呼ばれるのか」を単純な文字列検索だけで追いにくいことがあります。

保守時に見る点は、このあたりです。

コマンドIDは何か
ON_COMMANDはどのクラスにあるか
ON_UPDATE_COMMAND_UIはどこにあるか
現在アクティブなビューはどれか
Document/View構造を使っているか

11. ON_UPDATE_COMMAND_UIとは何か

MFC では、メニュー項目やツールバーボタンの有効/無効、チェック状態、表示テキストなどを更新するために、ON_UPDATE_COMMAND_UI を使うことがあります。

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_COMMAND(ID_EDIT_DELETE, &CMainFrame::OnEditDelete)
    ON_UPDATE_COMMAND_UI(ID_EDIT_DELETE, &CMainFrame::OnUpdateEditDelete)
END_MESSAGE_MAP()

void CMainFrame::OnUpdateEditDelete(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(CanDeleteCurrentItem());
}

これにより、削除できる状態のときだけメニューやボタンを有効にできます。

MFC アプリを使っていて、ボタンがなぜかグレーアウトする、メニューが押せない、チェック状態が変わる、といった挙動を調べるときは、ON_UPDATE_COMMAND_UI を探すと原因が見つかることがあります。

12. ダイアログベースのMFCアプリ

MFC で最も分かりやすい形の 1 つが、ダイアログベースアプリケーションです。

設定画面、簡単な業務ツール、装置操作画面などでは、ダイアログを中心に構成されていることがあります。

典型的には、CDialog または CDialogEx を継承します。

class CSettingsDialog : public CDialogEx
{
public:
    CSettingsDialog(CWnd* pParent = nullptr);

#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_SETTINGS_DIALOG };
#endif

protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();

    afx_msg void OnBnClickedOk();
    DECLARE_MESSAGE_MAP()

private:
    CString m_name;
    int m_interval;
};

ダイアログベースのコードでは、以下の要素がよく出てきます。

IDD_...        ダイアログリソースID
IDC_...        コントロールID
OnInitDialog   初期化処理
DoDataExchange コントロールとメンバー変数の関連付け
UpdateData     画面と変数の同期
ON_BN_CLICKED  ボタンクリック処理

ダイアログは見た目だけではなく、リソース、メンバー変数、メッセージマップ、初期化処理が組み合わさって動きます。

13. DDXとDDV

MFC のダイアログでよく出てくるのが、DDXDDV です。

DDX = Dialog Data Exchange
DDV = Dialog Data Validation

DDX はダイアログ上のコントロールと C++ のメンバー変数を対応づける仕組みで、DDV はその入力値を検証する仕組みです。

void CSettingsDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_NAME, m_name);
    DDX_Text(pDX, IDC_EDIT_INTERVAL, m_interval);
    DDV_MinMaxInt(pDX, m_interval, 1, 3600);
}

UpdateData(TRUE) を呼ぶと、画面の入力値がメンバー変数へ反映されます。

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

    // ここでは m_name や m_interval に画面入力値が入っている
    SaveSettings(m_name, m_interval);

    CDialogEx::OnOK();
}

逆に、UpdateData(FALSE) を呼ぶと、メンバー変数の値が画面へ反映されます。

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

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

    return TRUE;
}

MFC のダイアログで入力値がおかしいときは、このあたりを確認します。

DoDataExchangeにDDXが定義されているか
UpdateData(TRUE)を呼んでいるか
UpdateData(FALSE)を呼ぶタイミングは正しいか
DDVで入力が弾かれていないか
コントロールIDがリソースと一致しているか

14. Document/Viewアーキテクチャ

MFC の大きな特徴の 1 つが、Document/View アーキテクチャです。

これは、アプリケーションが扱うデータと、その表示を分離するための構造です。

CDocument
  データを保持する
  ファイルの読み書きを担当する
  複数のビューへ更新通知する

CView
  データを表示する
  ユーザー操作を扱う
  描画や選択状態を管理する

たとえば、テキストエディタ、図形エディタ、設定ファイル編集ツール、CAD のようなアプリでは、データと表示を分ける意味があります。

class CMyDocument : public CDocument
{
public:
    std::vector<Item> m_items;

    virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
    virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);
};

class CMyView : public CView
{
protected:
    virtual void OnDraw(CDC* pDC);

    CMyDocument* GetDocument() const;
};

ビュー側では、ドキュメントを取得して描画します。

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

    for (const auto& item : pDoc->m_items)
    {
        // pDCを使って描画する
    }
}

Document/View の利点は、同じデータを複数のビューで表示しやすいことです。

たとえば、同じデータをこんなふうに見せられます。

表形式ビュー
グラフビュー
詳細ビュー
プレビュー
印刷ビュー

ただし、単純な設定画面や小さなツールに Document/View を使うと、かえって構造が重く感じることもあります。

保守時には、そのアプリが Document/View を使っているのか、ダイアログ中心なのかを最初に見極めると、コードを追いやすくなります。

15. SDIとMDI

MFC では、Document/View と組み合わせて、SDI や MDI という構造がよく出てきます。

SDI = Single Document Interface
MDI = Multiple Document Interface

SDI は、基本的に 1 つのフレームで 1 つのドキュメントを扱う形です。

メインウィンドウ
  1つのドキュメント
  1つまたは複数のビュー

MDI は、1 つの親ウィンドウの中に複数の子ウィンドウを持ち、それぞれがドキュメントを扱う形です。

MDI親フレーム
  MDI子フレーム1 -> ドキュメント1
  MDI子フレーム2 -> ドキュメント2
  MDI子フレーム3 -> ドキュメント3

古い Windows アプリケーションでは、MDI がよく使われていました。

保守時には、クラス名を見れば構造の見当がつきます。

CFrameWnd       SDI系のフレーム
CMDIFrameWnd    MDI親フレーム
CMDIChildWnd    MDI子フレーム
CSingleDocTemplate SDI用ドキュメントテンプレート
CMultiDocTemplate  MDI用ドキュメントテンプレート

MFC のウィザードで作られたアプリでは、InitInstance の中に CSingleDocTemplateCMultiDocTemplate の登録処理があることが多いです。

16. リソースファイルを理解する

MFC アプリでは、.rc ファイルが非常に重要です。

.rc は Windows のリソースファイルです。

ここには、こういったものが定義されています。

ダイアログテンプレート
メニュー
アクセラレータキー
アイコン
ビットマップ
文字列テーブル
バージョン情報
ツールバー

また、resource.h には、リソースIDが定義されます。

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

MFC のコードでは、これらの ID を使って、リソースと C++ コードを結びつけます。

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

保守時にありがちな問題は、リソース ID の不整合です。

resource.hのIDが変わった
別ブランチのマージでIDが衝突した
ダイアログ上のコントロールIDとDDXのIDが合っていない
削除したはずのメニューIDが残っている
文字列テーブルのIDが重複している

MFC アプリの挙動を調べるときは、C++ コードだけでなく .rcresource.h も同時に見る必要があります。

17. Class Wizardと手書きコード

MFC には、Visual Studio の Class Wizard と深く結びついた歴史があります。

Class Wizard を使うと、メッセージハンドラー、DDX 変数、仮想関数のオーバーライドなどを自動生成できます。

そのため、MFC コードには、ツールが生成した形のコードが多く残っています。

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

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

新しい Visual Studio では見え方や生成される形が変わっていることもありますが、古いコードベースではこのようなコメントマーカーが残っていることがあります。

保守時に重要なのは、生成コードと手書きコードの境界を雑に壊さないことです。

メッセージマップを削除しない
DDXの対応を壊さない
リソースIDを不用意に変えない
古いClass Wizard前提のコメントをむやみに消さない

MFC では、単に C++ としてコンパイルが通るだけでは不十分です。 Visual Studio のリソースエディタや Class Wizard が期待する形も、ある程度保つ必要があります。

18. CStringと文字列

MFC で頻繁に登場する文字列クラスが CString です。

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

CString は、MFC/ATL 系のコードでよく使われる可変長文字列クラスです。現代 C++ では std::stringstd::wstring を使う場面が多いですが、MFC では API やコントロールとの相性から CString が多用されます。

保守時には、文字コードを意識する必要があります。

CString      プロジェクト設定に応じてCStringAまたはCStringW相当
CStringA     ANSI / MBCS系
CStringW     Unicode / UTF-16系
LPCTSTR      TCHARベースの文字列ポインタ
LPCSTR       char系
LPCWSTR      wchar_t系
std::string  通常はchar系
std::wstring wchar_t系

現在の Windows アプリでは基本的に Unicode を前提にしたほうが安全ですが、古い MFC アプリでは MBCS 前提のコードが残っていることがあります。

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

文字列変換のバグは、MFC アプリの保守でよく出ます。

特に注意したいのは、こういうケースです。

Shift_JIS前提のファイルを読む
Unicodeビルドへ切り替える
外部DLLがchar*を要求する
COMがBSTRを要求する
std::stringへ安易に変換して文字化けする

CString を見たら、単に「古い文字列クラスだ」と考えるのではなく、プロジェクトの文字セット設定、外部API、ファイル形式とセットで確認することが大切です。

19. CFileとCArchive

MFC には、ファイル操作やシリアライズのためのクラスもあります。代表的なのが CFileCArchive です。

CFile file;
if (file.Open(path, CFile::modeRead))
{
    CArchive ar(&file, CArchive::load);
    // arから読み込む
}

CArchive は、MFC のシリアライズ機構でよく使われます。

CDocument 派生クラスでは、Serialize をオーバーライドして、読み込みと保存を同じ関数に書くことがあります。

void CMyDocument::Serialize(CArchive& ar)
{
    if (ar.IsStoring())
    {
        ar << m_title;
        ar << static_cast<int>(m_items.size());
        for (const auto& item : m_items)
        {
            ar << item.Name;
            ar << item.Value;
        }
    }
    else
    {
        int count = 0;
        ar >> m_title;
        ar >> count;
        m_items.clear();
        for (int i = 0; i < count; ++i)
        {
            Item item;
            ar >> item.Name;
            ar >> item.Value;
            m_items.push_back(item);
        }
    }
}

MFC のシリアライズは便利ですが、長期運用では注意が必要です。

古いファイル形式との互換性
バージョン番号の管理
読み込み失敗時の復旧
例外処理
文字コード
エンディアン
構造体をそのまま保存していないか

独自バイナリ形式を長く使っている MFC アプリでは、Serialize が事実上のファイル仕様になっていることもあります。

この場合、コード変更前に、既存ファイルを読み込むテストデータを必ず用意したほうがよいです。

20. GDI描画とCDC

MFC の画面描画では、Windows の Device Context を扱うための CDC クラスがよく使われます。CView::OnDraw では、引数として CDC* が渡されます。

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

ペンやブラシを使う場合は、選択と復元に注意します。

void CMyView::OnDraw(CDC* pDC)
{
    CPen pen(PS_SOLID, 1, RGB(0, 0, 0));
    CPen* pOldPen = pDC->SelectObject(&pen);

    pDC->MoveTo(10, 10);
    pDC->LineTo(100, 100);

    pDC->SelectObject(pOldPen);
}

GDI オブジェクトまわりでは、こんなミスが問題になります。

SelectObjectした後に元へ戻していない
GDIオブジェクトを大量に作って破棄していない
OnPaintとOnDrawの役割を混同している
ダブルバッファリングしておらずちらつく
高DPIで固定ピクセル前提の描画が崩れる

MFC の描画バグでは、C++ のロジックだけではなく、Windows の GDI リソース、再描画タイミング、DPI、フォントサイズを確認する必要があります。

21. モーダルダイアログとモードレスダイアログ

MFC では、ダイアログの出し方にも注意が必要です。

モーダルダイアログは、DoModal で表示します。

CSettingsDialog dlg(this);
if (dlg.DoModal() == IDOK)
{
    // OK時の処理
}

この場合、ダイアログが閉じるまで呼び出し元は待ちます。

一方、モードレスダイアログは、作成後も呼び出し元の処理が戻ります。

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

モードレスダイアログでは、寿命管理が重要です。

newしたダイアログをいつdeleteするか
親ウィンドウが先に破棄されないか
ダイアログ側でPostNcDestroyを使うか
二重生成されないか
閉じた後のポインタが残っていないか

MFC のクラッシュ調査では、モードレスダイアログの寿命問題が原因になることがあります。

22. C++オブジェクトとWindowsハンドルの寿命

MFC で非常に重要なのが、C++ オブジェクトと Windows ハンドルの寿命の違いです。たとえば CWnd は C++ オブジェクトですが、実際のウィンドウは Windows が HWND として管理しており、この 2 つは常に同時に作られて同時に消えるわけではありません。

CWndオブジェクトはあるがHWNDがまだない
HWNDは破棄されたがCWndオブジェクトは残っている
一時的なCWndラッパーが作られている
Attach/Detachでハンドルを付け替えている

たとえば、こんなコードは要注意です。

CWnd* pWnd = GetDlgItem(IDC_SOME_CONTROL);
// pWndをメンバーに保存して後で使う

GetDlgItem で得たポインタを長期間保持する場合、ウィンドウの破棄後に参照してしまう危険があります。

必要なら、毎回 GetDlgItem するか、コントロール用のメンバー変数を DDX で管理するほうが安全な場合があります。

DDX_Control(pDX, IDC_LIST_ITEMS, m_listItems);

MFC では、ポインタが非 null でも安全とは限りません。

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

この感覚は、MFC 保守でとても重要です。

23. スレッドとUI更新

Windows の UI は基本的に作成された UI スレッドで操作する必要があり、MFC アプリでも同じです。ワーカースレッドから直接 UI コントロールを操作すると、不安定な動作やクラッシュの原因になります。

避けたい例です。

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

    // ワーカースレッドから直接UIを触るのは避ける
    pDlg->SetDlgItemText(IDC_STATUS, _T("Done"));

    return 0;
}

一般的には、PostMessage などで UI スレッドへ通知します。

constexpr UINT WM_APP_WORK_DONE = WM_APP + 1;

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

    // 重い処理

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

UI 側では、メッセージマップで受けます。

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_MESSAGE(WM_APP_WORK_DONE, &CMyDialog::OnWorkDone)
END_MESSAGE_MAP()

LRESULT CMyDialog::OnWorkDone(WPARAM, LPARAM)
{
    SetDlgItemText(IDC_STATUS, _T("Done"));
    return 0;
}

MFC のスレッド周りでは、以下を確認します。

UIをワーカースレッドから直接触っていないか
ウィンドウ破棄後にPostMessageしていないか
スレッド終了待ちでUIスレッドを止めていないか
共有データのロックは適切か
AfxBeginThreadの戻り値と寿命を誤解していないか

24. MFC DLLとモジュール状態

MFC で DLL を作る場合、モジュール状態という概念が出てきます。

MFC DLL からリソースを読み込む、ダイアログを表示する、拡張 DLL を作る、といった場面では、どのモジュールのリソースを見に行くかが問題になります。

MFC DLL の関数入口で、こんなマクロを見ることがあります。

AFX_MANAGE_STATE(AfxGetStaticModuleState());

これは、MFC が正しいモジュール状態を使えるようにするためのものです。

このマクロを忘れると、こういう不具合につながります。

DLL内のダイアログリソースが見つからない
文字列リソースが別モジュールから読まれる
アイコンやメニューが見つからない
デバッグ時だけ動いてリリースで壊れる

MFC DLL を保守するときは、EXE、通常 DLL、拡張 DLL、リソース DLL の関係を整理することが重要です。

特に、非 MFC アプリから MFC DLL を呼んでいる場合や、プラグイン構成になっている場合は注意が必要です。

25. MFCを静的リンクするか共有DLLで使うか

MFC アプリでは、プロジェクト設定に「Use of MFC」があります。

代表的な選択肢はこの 2 つです。

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

共有 DLL を使う場合、実行環境に対応する MFC ランタイムや Visual C++ ランタイムが必要です。

静的リンクの場合、配布物は単純に見えることがありますが、実行ファイルサイズや更新、セキュリティ修正の取り込み、ライセンスや再配布条件などを考える必要があります。

どちらが常に正解というものではありません。

判断材料はこのあたりになります。

配布先にVisual C++ Redistributableを入れられるか
アプリを単一exeに近づけたいか
セキュリティ更新をどう反映するか
複数アプリで同じランタイムを共有するか
インストーラーを用意できるか
対象Windowsのバージョンは何か

保守では、まず現在の設定を確認することが大事です。

Configuration Properties
  General
    Use of MFC

加えて、Runtime Library の設定も見ます。

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

MFC と CRT のリンク設定が混在すると、ライブラリ境界でメモリ確保/解放の問題が出ることがあります。

26. Unicode、MBCS、TCHAR

古い MFC コードでは、TCHARLPCTSTR_T() マクロがよく出てきます。

CString title = _T("設定");
SetWindowText(title);

これは、Unicode ビルドと MBCS ビルドの両方に対応するための書き方です。

Unicodeビルド
  TCHAR    -> wchar_t
  LPCTSTR  -> const wchar_t*
  _T("...") -> L"..."

MBCSビルド
  TCHAR    -> char
  LPCTSTR  -> const char*
  _T("...") -> "..."

現在は Unicode ビルドが一般的ですが、古いアプリでは MBCS 前提の処理が残っていることがあります。

特に、外部ファイル、通信プロトコル、古い DLL、データベース接続、シリアル通信などでは、文字コードの前提を確認する必要があります。

注意したいのは、Unicode 化を単純な置換作業と考えないことです。

char配列のサイズはバイト数か文字数か
strlenを使っていないか
sizeof(buffer)を文字数として使っていないか
外部APIはUTF-16を受けるのかShift_JISを受けるのか
ファイル保存形式は変わってよいのか

MFC の文字列周りを直すときは、画面表示だけではなく、ファイル互換性と外部連携まで確認します。

27. MFCとCOM/OLE/ActiveX

MFC は、COM、OLE、ActiveX と関係の深いアプリケーションでも使われてきました。

古い業務アプリでは、以下のような要素が残っていることがあります。

OLE Automation
ActiveX Control
COMサーバー
COMクライアント
IDispatch
BSTR
VARIANT
COleDispatchDriver
COleVariant

MFC アプリの起動処理には、このようなコードが出てくることがあります。

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

COM/OLE を使っている場合、MFC の問題に見えて、実際には COM の初期化、スレッドモデル、参照カウント、登録情報、32bit/64bit の違いが原因になっていることがあります。

特に注意したいのは、32bit ActiveX や COM コンポーネントです。

32bit MFCアプリからは32bit COMを使う
64bit MFCアプリからは64bit COMを使う
32bit/64bitのCOM登録は別
古いActiveXが64bit対応していないことがある

MFC アプリを x64 化するときは、UI コードだけでなく、COM/OLE 依存も必ず確認します。

28. 高DPI対応と現代のWindows

古い MFC アプリを現代の Windows で動かすと、高 DPI 環境で表示が崩れることがあります。

たとえば、こんな問題です。

文字が切れる
ボタンが小さすぎる
固定ピクセル描画がずれる
複数モニターで拡大率が違うと崩れる
古いビットマップがぼやける
ダイアログのレイアウトが詰まる

Windows デスクトップアプリでは、アプリケーションが DPI 対応モードを明示する必要があります。

MFC アプリでも、マニフェスト、リソース、描画コード、フォント、レイアウトを確認する必要があります。

特に、固定座標を直書きしたこういうコードは、高 DPI で問題になりやすいです。

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

固定ピクセル前提の座標は、DPI が変わると見た目が崩れます。

保守時の確認観点はこうです。

アプリケーションマニフェストのDPI設定
ダイアログリソースのフォント
固定ピクセル描画
画像リソースの解像度
複数モニターでの動作
Windows 10 / Windows 11での表示

MFC の高 DPI 対応は、単にプロジェクト設定を変えるだけで終わるとは限りません。 古い UI では、実際の画面確認とレイアウト修正が必要になります。

29. 例外処理とエラー処理

MFC には、独自の例外クラスやエラー処理の作法があります。

古いコードでは、このようなマクロを見かけます。

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

現代 C++ の try / catch と混在しているコードもあります。

try
{
    DoSomething();
}
catch (const std::exception& ex)
{
    // ログ出力
}

MFC 保守で注意したいのは、以下の点です。

MFC例外とC++標準例外が混在していないか
例外オブジェクトの寿命を誤解していないか
古いTHROW/CATCHマクロを理解しているか
戻り値エラーと例外が混在していないか
AfxMessageBoxだけでログが残らない状態になっていないか

業務アプリでは、画面にエラーメッセージを出すだけでなく、ログ、操作履歴、入力値、外部接続状態を残すことが重要です。

古い MFC アプリでは、エラーが AfxMessageBox だけで終わっていることがあります。

AfxMessageBox(_T("保存に失敗しました"));

保守性を上げるなら、UI 表示とログ記録を分けたほうがよいです。

LogError(_T("Save failed"), path);
AfxMessageBox(_T("保存に失敗しました。ログを確認してください。"));

30. MFCと現代C++をどう共存させるか

MFC アプリを保守しているからといって、すべてを古い C++ の書き方に合わせる必要はありません。

UI 層は MFC の作法を尊重しつつ、ドメインロジックや計算処理は現代 C++ で整理できます。

たとえば、こう分けます。

MFC層
  CDialog
  CView
  CDocument
  CString
  メッセージマップ
  リソース操作

非MFC層
  std::string / std::wstring
  std::vector
  std::optional
  std::variant
  std::filesystem
  ユニットテスト可能なクラス
  ビジネスロジック

悪い形は、すべての処理がダイアログクラスに詰め込まれている状態です。

void CMainDialog::OnBnClickedExecute()
{
    // 入力取得
    // ファイル読み込み
    // 通信
    // 計算
    // DB更新
    // 画面更新
    // ログ出力
    // 例外処理
}

このようなコードは、変更しにくく、テストしにくく、バグ調査も難しくなります。

改善するなら、MFC クラスからロジックを外へ出します。

void CMainDialog::OnBnClickedExecute()
{
    if (!UpdateData(TRUE))
    {
        return;
    }

    ExecuteRequest request;
    request.Name = ToStdWString(m_name);
    request.Interval = m_interval;

    ExecuteResult result = m_service.Execute(request);

    m_status = ToCString(result.Message);
    UpdateData(FALSE);
}

このようにすると、m_service.Execute は MFC なしでテストできます。

MFC 既存資産を保守するうえで最も効く改善は、UI クラスからロジックを少しずつ切り出すことです。

31. テストしやすいMFCコードにする

MFC アプリは、そのままだとユニットテストしにくいことが多いです。

理由は、UI、Win32、ファイル、通信、データベース、グローバル状態が密結合になりやすいからです。

テストしやすくするための考え方はこうです。

CDialogやCViewを直接テストしようとしない
まず非UIロジックを切り出す
MFC型を境界で変換する
ファイルや通信をインターフェース化する
画面イベントハンドラーを薄くする

たとえば、純粋な C++ クラスに処理を移します。

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

MFC 側は、入力と出力だけを担当します。

void CPriceDialog::OnBnClickedCalculate()
{
    if (!UpdateData(TRUE))
    {
        return;
    }

    std::vector<int> prices = ParsePrices(ToStdWString(m_input));
    int total = m_calculator.CalculateTotal(prices);

    m_result.Format(_T("%d"), total);
    UpdateData(FALSE);
}

この構造にすると、PriceCalculatorParsePrices は通常の C++ テストフレームワークでテストできます。

MFC アプリ全体を一気に作り直す必要はなく、まずはイベントハンドラーの中からテスト可能な処理を外へ出すだけでも効果があります。

32. ビルド環境を固定する

MFC アプリの保守では、ビルド環境の固定が重要です。

古いコードベースでは、以下の違いでビルド結果が変わることがあります。

Visual Studioのバージョン
MSVCツールセットのバージョン
Windows SDKのバージョン
MFC/ATLコンポーネントの有無
x86 / x64 / ARM64
Debug / Release
Unicode / MBCS
MFCの静的リンク / 共有DLL
ランタイムライブラリ設定
プリコンパイル済みヘッダー

MFC アプリでは、stdafx.hpch.h に多くの依存が集まっていることもあります。

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

あるファイルだけコンパイル設定が違う、プリコンパイル済みヘッダーの設定がずれている、といった理由でビルドが壊れることがあります。

保守プロジェクトでは、この情報を README に明記しておくと後が楽になります。

必要なVisual Studioバージョン
必要なワークロードと個別コンポーネント
必要なWindows SDK
ターゲットプラットフォーム
MFCのリンク方式
ビルド手順
CIの実行方法
配布物の作り方

「自分のPCではビルドできる」を卒業することが、MFC 保守の第一歩です。

33. CIでMFCをビルドする

MFC アプリも、CI でビルドできます。

ただし、CI 環境に MFC コンポーネントが入っている必要があります。

Visual Studio Build Tools を使う場合、MFC/ATL のコンポーネント ID を指定してインストールする必要があります。

確認したいポイントを挙げます。

Build ToolsにMFCが入っているか
対象ツールセットがプロジェクトと一致しているか
Windows SDKが入っているか
x86ビルドとx64ビルドを両方確認しているか
リソースコンパイラが動くか
署名処理があるか
インストーラー生成もCI対象か

MFC アプリでは UI テストまで自動化するのは簡単ではありませんが、少なくともこのあたりの自動化は有効です。

Debug / Release のビルド
x86 / x64 のビルド
静的解析
単体テスト
インストーラー作成
成果物のハッシュ保存
依存DLLの確認

長期保守では、ビルドできる状態を保つだけでも大きな価値があります。

34. MFCアプリのデバッグで見る場所

MFC アプリで不具合を追うときは、この順番で見ると効率的です。

1. どの画面か
2. ダイアログか、Viewか、Frameか
3. 操作に対応するリソースIDは何か
4. メッセージマップでどの関数に行くか
5. UpdateDataの向きは正しいか
6. Document/ViewならDocumentの状態は何か
7. コマンドルーティングで別クラスに流れていないか
8. ワーカースレッドからUIを触っていないか
9. 例外やエラーがAfxMessageBoxだけで握りつぶされていないか
10. リリースビルド固有の未初期化や寿命問題はないか

たとえば、「ボタンを押しても何も起きない」という不具合なら、ここを確認します。

ボタンのIDCが正しいか
ON_BN_CLICKEDが存在するか
ハンドラーのシグネチャは正しいか
ダイアログリソースが別のものではないか
ボタンが無効化されていないか
処理途中でUpdateDataが失敗していないか
例外が握りつぶされていないか

「メニューが押せない」なら、こちらです。

ON_UPDATE_COMMAND_UIで無効化されていないか
コマンドIDが重複していないか
アクティブビューが想定通りか
Frame/View/Document/Appのどこにハンドラーがあるか

MFC は、表面上のイベントと実際の処理がマクロやルーティングでつながっているため、慣れるまでは呼び出し経路を図にすると理解しやすいです。

35. よくある落とし穴

MFC 保守でよくある落とし穴を整理します。

メッセージマップを見ずに関数呼び出しだけ検索する
CWnd*が非nullなら有効だと思い込む
HWNDの寿命とC++オブジェクトの寿命を混同する
UpdateData(TRUE/FALSE)の向きを間違える
ON_UPDATE_COMMAND_UIで無効化されていることに気づかない
resource.hのID衝突に気づかない
ワーカースレッドからUIを直接触る
CStringとstd::stringの変換で文字化けする
MBCS前提コードをUnicode化で壊す
MFC DLLでAFX_MANAGE_STATEを忘れる
x86前提のCOM/ActiveXをx64化で壊す
GDIオブジェクトの選択/解放を誤る
高DPIで固定座標レイアウトが崩れる

MFC の不具合は、C++ の文法だけ見ていても分からないことがあり、Windows のメッセージ、リソース、ハンドル、モジュール、ランタイム設定まで含めて見る必要があります。

36. 新規開発でMFCを選ぶべきか

完全な新規開発で MFC を選ぶかどうかは、慎重に判断したほうがよいです。

MFC を選ぶ理由があるとすれば、こういう場合です。

既存MFCコードと密に連携する必要がある
既存のMFC部品や画面を再利用したい
Win32/GDI/COMに非常に近い制御が必要
社内にMFC保守スキルが十分ある
対象がWindowsデスクトップに限定されている
長期的な移行計画上、まずMFCで増築する必要がある

逆に、以下の場合は別の選択肢を検討したほうがよいです。

モダンなUIを作りたい
柔軟なレイアウトやアニメーションが必要
Web連携やクラウド連携が中心
テスト容易性を重視したい
若手開発者が参加しやすい技術を選びたい
クロスプラットフォーム対応が必要
アクセシビリティや高DPIを最初から重視したい

MFC は「今から覚える価値がない」技術でも「新しいから選ぶ」技術でもなく、既存 Windows ネイティブ資産と向き合うための技術です。

37. MFCから移行するときの考え方

MFC アプリを別の技術へ移行したい場合、いきなり全面刷新を目指すと失敗しやすいです。

まず、アプリをこう分解して考えます。

UI
ビジネスロジック
ファイル形式
通信処理
データベース処理
デバイス制御
印刷
COM/OLE連携
設定管理
ログ

このうち、最も MFC に依存しているのは UI です。

一方、ビジネスロジックやファイル処理は、切り出せる可能性があります。

移行の現実的な順番は、こうなります。

1. ビルド環境を再現する
2. 既存動作をテストデータで固定する
3. UIイベントハンドラーからロジックを切り出す
4. 非MFCのC++ライブラリに寄せる
5. 自動テストを追加する
6. 外部仕様を文書化する
7. 必要な画面から段階的に置き換える

「MFC をやめる」こと自体を目的にするより、「MFC に閉じ込められている重要ロジックを外へ出す」ことを目的にしたほうが、成功しやすいです。

38. MFCコードを読むときの入口

既存の MFC プロジェクトを初めて読むときは、このあたりのファイルから見ていきます。

*.vcxproj
  ツールセット、MFC設定、文字セット、ランタイム設定を見る

resource.h
  リソースIDを見る

*.rc
  ダイアログ、メニュー、文字列、アイコンを見る

*App.cpp / *App.h
  CWinApp派生クラスとInitInstanceを見る

MainFrm.cpp / MainFrm.h
  メインフレームとメニュー/ツールバーを見る

*Doc.cpp / *Doc.h
  Document/Viewならデータ構造と保存処理を見る

*View.cpp / *View.h
  描画とユーザー操作を見る

*Dlg.cpp / *Dlg.h
  ダイアログ、DDX、ボタン処理を見る

次に、よく使われる検索キーワードです。

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

このあたりを検索すると、アプリケーションの動きが見えやすくなります。

39. MFCを保守するときの設計方針

MFC 既存資産を長く保守するなら、こんな方針が有効です。

UIイベントハンドラーを薄くする
CStringやCWndを非UI層へ漏らしすぎない
ビジネスロジックを通常のC++クラスへ移す
ファイル形式の互換性テストを作る
x86/x64の違いを明確にする
リソースIDの変更をレビューする
MFC DLLのモジュール状態を確認する
ログを整備する
CIでビルドを固定する
高DPIやWindows 11での画面確認を定期的に行う

特に、CDialogCView に処理を詰め込みすぎないことが重要です。

MFC の画面クラスは、入力、表示、イベント配送に集中させます。

画面から値を取る
サービスへ渡す
結果を画面に反映する

この程度に保てると、MFC であってもかなり保守しやすくなります。

40. 実務チェックリスト

MFC アプリを扱うときのチェックリストです。

Visual Studioのバージョンは明確か
MFC/ATLコンポーネントはインストールされているか
x86/x64のターゲットは明確か
Unicode/MBCSの設定は把握しているか
MFCは静的リンクか共有DLLか
必要なVisual C++ Redistributableは何か
resource.hと.rcをレビュー対象にしているか
メッセージマップでイベント経路を追っているか
UpdateDataの向きは正しいか
Document/View構造を使っているか
モードレスダイアログの寿命管理は安全か
ワーカースレッドからUIを直接触っていないか
MFC DLLでAFX_MANAGE_STATEが必要な箇所はないか
高DPI環境で画面確認しているか
COM/ActiveX依存は32bit/64bit対応しているか
ログは残るか
非UIロジックをテストできるか

MFC は慣れるまでは独特に見えますが、見るべき場所が分かると、かなり規則的に読めます。

41. まとめ

MFC は、Windows のネイティブデスクトップアプリケーションを C++ で構築するための歴史あるフレームワークです。新規開発の主流ではなくなりましたが、既存資産の保守、ビルド環境更新、機能追加、段階的移行では、今でも重要な技術です。

MFC を理解するうえで特に大事なのは、以下の点です。

MFCはWin32 APIをC++で扱いやすくしたもの
CWinAppがアプリ全体を管理する
CWndとHWNDの寿命は同じではない
メッセージマップでイベントと関数が結びつく
Document/Viewはデータと表示を分ける仕組み
DDX/DDVはダイアログの値同期と検証に使う
リソースファイルとresource.hが非常に重要
CStringやTCHARは文字コード設定とセットで理解する
MFC DLLではモジュール状態に注意する
古いMFCアプリは高DPI、x64、CI、テストの観点で見直す価値がある

MFC を扱うときは、「古いから悪い」と決めつけるよりも、まず構造を理解することが大切です。既存の MFC アプリには、長年の業務知識、顧客ごとの仕様、デバイス連携、ファイル互換性が詰まっていることがあります。その価値を守りながら少しずつ保守しやすくするには、MFC の作法を理解したうえで、非 UI ロジックを切り出し、ビルドとテストを整えるのが現実的です。

あえて一言にまとめるなら、こうなります。

Windows ネイティブアプリの仕組みを、C++ のクラスとフレームワークで扱うための基盤。

この視点を持つと、MFC は単なる古い技術ではなく、既存 Windows 資産を安全に読み解くための手がかりになります。

参考

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る