MFCの仕組みを詳しく調べてみる

MFCのダイアログベースのアプリケーションを作成した際にできるファイルを詳しく調べてみます。

これにより、どのようにMFCが動作するかを理解できるようになります。

アプリケーションウィザードの設定

設定は、

  • ダイアログベース
  • Unicodeライブラリを使用しない(使用するのチェックを外す)
  • メインフレームのスタイルにおけるチェックをすべてはずす
  • 高度な機能のチェックをすべてはずす

とします。つまり最小の機能にします。ちなみにプロジェクト名は「sample」にしました。

作成されたファイル

すると以下の様なファイルとフォルダが作成されます。

  • sample.sln
  • sample.ncb
  • sampleフォルダ

一つ目のsample.slnはソルーションファイルと呼ばれるファイルです。1つまたは複数のプロジェクトを管理する際にMFCが使用するファイルです。

2つものsample.ncbはシンボル管理のためにVisual Studioが使うファイルのことです。

これら2つのファイルはVisutal Studio側の都合で使用するので、プログラマはあまり気にしなくてもOKです。

最後のsampleフォルダが重要となります。

sampleフォルダの中身

では、sampleフォルダの中身を見ていきます。

まず、ヘッダーファイルが5つあります。

  • targetver.h
  • stdafx.h
  • sample.h
  • sampleDlg.h
  • Resource.h

targetver.h

targetver.hはプログラムが使用可能な最低限のバージョンを設定します。

例えば、ウインドウズにおける最低限のバージョンは何かとか、エクスプローラーの最低限のバージョンはという感じです。

実際にみてみると、

#pragma once
#ifndef WINVER
#define WINVER 0x0600
#endif

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif

#ifndef _WIN32_WINDOWS
#define _WIN32_WINDOWS 0x0410
#endif

#ifndef _WIN32_IE
#define _WIN32_IE 0x0700
#endif

となっています。0x0600がWindows Vistaで、0x0410がWindows 98です。そして、IEの0x0700はInternet Explorer 7.0を意味します。#pragma onceは重複宣言を回避するためのものです。もし、再び#pragma once以下と同じ物が宣言されたら無視してくれます。

stdafx.h

stdafx.hはよく使用するけど、あまり内容を変更しないインクルードファイルが記述してあります。

プログラムを変更すると、変更のたびにコンパイルしなければなりません。

しかし、変更していないファイルまでいちいちコンパイルしていたら時間がかかります。

そのため、変更しないものをstdafx.hでまとめておけば余計なコンパイルを減らして時間の節約になります。

これあがstdafx.hの目的です。内容は以下のように記述してあります。

#pragma once
#ifndef _SECURE_ALT
#define _SECURE_ALT 1
#endif

#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN
#end if

#include "targetver.h"

#define _ALT_CSTRING_EXPLICIT_CONSTRUCTORS
#define _AFX_ALL_WARNINGS

#include ;
#include ;

ここではいろいろな値を#defineで定義したり解除したりしています。そして、はじめに述べたtargetver.hをインクルードしてあります。

また、afxwin.hとafxext.hもインクルードしてあります。afxwin.hはMFCのコンポーネントが記載されているヘッダーです。これによりメッセージボックスなどの基本的な命令を実行できるようになります。

そして、afxext.hでは、MFCをバージョンアップさせた際に追加したコンポーネントが入っています。

Resource.h

次にResorce.hを見ていきましょう。

Resorce.hにはリソースビューに出てくるコンポーネントの番号が定義されています。

リソースビューには

  • IDD_SAMPLE_DIALOG
  • IDR_MAINFRAME

があります。これらの定義がResorce.hで

#define IDR_MAINFRAME 128
#define IDD_SAMPLE_DIALOG 102

と書かれています。このようにResorce.hにはリソースの定義が書かれていくファイルです。Visual Studioはリソースをいちいちコードにしなくても、ボタンなどを配置するだけで自動でやってくれるというわけですね。

ですから、プログラムを作る際にResorce.hを開くことはないわけです。

sample.hとsample.cpp

続いて、sample.hです。ソースは以下のようになっています。

#pragma once

#ifndef __AFXWIN_H
#error "PCHに対してこのファイルをインクルードする前に'stdafx.h'をインクルードしてください"
#endif

#include "resource.h"

class CsampleApp : public CWinAppEx
{
public:
    CsampleApp();
public:
    virtual BOOL InitInstance();
    DECLARE_MESSAGE_MAP()
};
extern CsampleApp theApp;

はじめの__AFXWIN_Hの部分は、sample.hを使うにはstdafx.hをインクルードする必要があることを述べています。

そして、resource.hもインクルードしています。

次にCsampleAppというクラスを定義しています。このクラスはCWinAppExクラスから継承されています。

このCsampleAppでは、コンストラクタ、InitInstance()というメソッドとDECLARE_MESSAGE_MAPというマクロが宣言してあります。

こららについてはsample.cppに書かれています。まず、MESSAGE_MAPについては

BEGIN_MESSAGE_MAP(CsampleApp,CWinAppEx)
    ON_COMMAND(ID_HELP,&CWinApp::OnHelp)
END_MESSAGE_MAP()

となっています。ヘルプが押された際のイベントについて書いてあります。

コンストラクタについては

CsampleApp::CsampleApp()
{
}

となっており、初期状態ではなにもありません。InitInstance()では

BOOL CsampleApp::InitInstance()
{
CWinAppEx::InitInstance();
SetRegistryKey(_T("アプリケーション ウィザードで生成されたローカル アプリケーション");
CsampleDlg dlg;
m_pMainWnd=&dlg;
INT_PTR nResponse=dlg.DoModal();
if(nResponse==IDOK)
{
}
else if(nResponse==IDCANCEL)
{
}
return FALSE;
}

ここでは、CsampleDlg dlg;でダイアローグクラスをつくり、dlg.DoModal()でダイアローグを生成しています。

ダイアローグのOKが押されると、if内の命令が実行されます。そして、Cancelが押されるとelse内の命令が実行されます。

このメソッドは

Csample theApp;

でインスタンスが生成されると実行されます。よって、プログラムを実行されるとはじめに実行されます。

sampleDlg.hとsampleDlg.cpp

続いて、sampleDlg.hを見ていきます。sampleDlgには以下のようにCsampleDlgクラスが定義されています。

class CsampleDlg : public CDialog
{
public:
    CsampleDlg(CWnd* pParent=NULL);
    enum{IDD=IDD_SAMPLE_DIALOG};
protected:
    virtual void DoDataExchange(CDataExchange* pDX);
protected:
    HICON m_hIcon;
    virtual BOOL OnInitDialog();
    afx_msg void OnPain();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
};

では、sampleDlg.cppでのこれらの説明をみていきます。まず、CsampleDlgのコンストラクタは

CsampleDlg::CsampleDlg(CWnd* pParent) : CDialog(CsampleDlg::IDD, pParent)
{
    m_hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

となっており、アイコンを取得しています。ダイアローグの左上に表示されるアイコンですね。

続いて、

BEGIN_MESSAGE_MAP(CsampleDlg,CDialog)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
END_MESSAGE_MAP()

でイベント処理について書かれています。ON_WM_PAINTは画面描写が必要なときに起こります。

BOOL CsampleDlg::OnInitDialog()
{
CDialog::OnInitDialog();
SetIcon(m_hIcon,TRUE);
SetIcon(m_hIcon,FALSE);
return TRUE;
}

この処理は、インスタンスが生成された際に起こるメソッドで、アイコンの設定をしています。

void CsampleDlg::OnPaint()
{
if(IsIconic())
{
    CPaintDC dc(this);
    SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()),0);
    int cxIcon=GetSystemMetrics(SM_CXICON);
    int cyIcon=GetSystemMetrics(SM_CYICON);
    CRect rect;
    int x=(rect.Width()-cxIcon+1)/2;
    int y=(rect.Height()-cyIcon+1)/2;
    dc.DrawIcon(x,y,m_hIcon);
}
else
{
    CDialog::OnPaint();
}
}

IsIconicでは、最小化されていると0が返されます。よって、最小化されていないとif内の処理がされます。

if内ではアイコンの描写を行なっています。

プログラムの実行手順

以上のことをまとめると以下のようにプログラムが実行されることがわかります。

  1. Csample theApp;よりtheAppが生成される
  2. BOOL CsampleApp::InitInstance()が実行される
  3. ダイアローグが呼び出される
  4. BOOL CsampleDlg::OnInitDialog()が実行される

著者:安井 真人(やすい まさと)