開発 Tips (C/C++/Visual C++/MFC/Windows プログラミング)


Visual C++/MFC

開発 Tips


凡例:
本頁内 : 頁内へのリンク
別頁 : サイト内の別頁へのリンク
外頁 : 外部サイトへのリンク

【Q&A】 サンプルが載った Web サイト

【Q】 MSDN には有用なサンプルが有りますが,そういう Web サイトって有りませんか.

【A】 以下はどうでしょう.

・CodeGuru

http://www.codeguru.com/

・MSDN Online Japan

http://www.microsoft.com/japan/developer

・Microsoft Systems Journal Homepage

http://www.microsoft.com/msj/

・VC++によるWin32プログラミングTips

http://www.expertmg.com/html/cti/vctips/

・Belution.com Visual C++ の Tips を公開

http://www.belution.com/worldwide/ja/


【Q&A】 ANSI C/C++ でプログラミングしたい

【Q】 移植性を高める為に VC++ の独自規格でなく ANSI C/C++ でプログラミングしたいのですが.

【A】 完全には無理ですが,コンパイル オプション /Za で,Microsoft 独自の (ANSI C 標準規格に対する) 拡張機能を無効に出来るようです (或いはプロジェクトの設定で Microsoft 言語拡張機能を無効にする).これではどうでしょうか.


【Q&A】 MFC アプリで別スレッドに CWnd* を渡す

【Q】 MFC 使用のアプリケーションで,別スレッドに CWnd のポインタを渡して処理をしようとした処,アサーションに失敗してしまいます.どうしたら良いでしょうか.

【A】 MFC ではスレッド毎にハンドルと MFC のオブジェクトの対応表を保持しています.即ち別スレッドでは別の対応表を見に行く事に成り,うまく動作しません.
別スレッドにはハンドルを直接渡すようにしましょう.クラスのメンバ関数等を呼び出したい場合には SendMessage が安全です.


【Q&A】 Cpp ファイルから対応する H ファイルを簡単に開く

【Q】現在 VC++ 6 を使っていますが,昔の VC++ のデベロッパー スタジオのように現在の Cpp ファイルから対応する H ファイルを簡単に開く方法はないでしょうか.

【A】以下のようなマクロを使うのはどうでしょう.

1.「ツール」-「マクロ」で例えば "OpenCppH" という名前でマクロを「編集」

'H ファイルと C/Cpp ファイルの表示切替
Sub OpenCppH()
    sn = ActiveDocument.FullName '現在編集中のファイル名
    cl = len(sn) '現在のファイル名の文字数

    '現在が H なら Cpp を表示
    if right(sn, 2) = ".h" then
        s2 = left(sn, cl - 2) + ".cpp" 'Cpp ファイル名
        Documents.Open s2, "Text" '開く
    end if

    '現在が Cpp なら H を表示
    if right(sn, 4) = ".cpp" then
        s2 = left(sn, cl - 4) + ".h" 'H ファイル名
        Documents.Open s2, "Text" '開く
    end if
End Sub

* 2002/07/10 F山 さん から, 上記ではヘッダーファイル に対応する cpp ファイルが無い場合等にエラーになるとの御指摘と,改良版のマクロを頂きました.F山 さん 有難う御座いました.御許可を頂いたので以下に改良版を掲載します.

'H ファイルと C/Cpp ファイルの表示切替 (改良版)
Sub OpenCppH()
    sn = ActiveDocument.FullName '現在編集中のファイル名
    cl = len(sn) '現在のファイル名の文字数
    set fs = CreateObject("Scripting.FileSystemObject") 'FileSystemObject

    '現在が H なら Cpp を表示
    if right(sn, 2) = ".h" then
        s2 = left(sn, cl - 2) + ".cpp" 'Cpp ファイル名
        if fs.FileExists(s2) = false then
            exit Sub 'ファイルが存在しないときは終了
        end if
        Documents.Open s2, "Text" '開く
    end if

    '現在が Cpp なら H を表示
    if right(sn, 4) = ".cpp" then
        s2 = left(sn, cl - 4) + ".h" 'H ファイル名
        set fs = CreateObject("Scripting.FileSystemObject") 'FileSystemObject
        if fs.FileExists(s2) = false then
            exit Sub 'ファイルが存在しないときは終了
        end if
        Documents.Open s2, "Text" '開く
    end if
End Sub


2.「ツール」-「カスタマイズ」-「キーボード」-「マクロ」-「OpenCppH」で御好みのショートカット キーを設定.

* 2003/09/04 Microsoft Visual Studio .NET2003 に移植したバージョンを,casper さんが以下で公開されています.
Microsoft Visual Studio .NET2003用マクロ詰め合わせ
Microsoft Visual Studio .NET2003 に便利と思われる機能を追加するマクロ詰め合わせ

【Q&A】 ダイアログ ボックスでアクセラレータ キーが効かない

【Q】ダイアログ ボックス ベースのアプリケーションを作成しています.
ところがアクセラレータ テーブルを作成してあるのに,何故か設定したキーが効いてくれません.どうしたら良いでしょうか.

【A】例えば,そのダイアログを CTestDlg とすると,

(1) TranslateAccelerator を行うメンバー関数を追加する

リソースのアクセラレータの ID を IDR_ACCELERATOR1 とすると,

BOOL CTestDlg::TranslateAccelerator(MSG* pMsg)
{
   // アクセラレータを一回だけロードする
   static HACCEL hAccel = HACCEL(0);
   if (hAccel == HACCEL(0)) {
       hAccel = ::LoadAccelerators(::AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
       ASSERT(hAccel != HACCEL(0));
   }
   // API の TranslateAccelerator を呼ぶ
   return ::TranslateAccelerator(GetSafeHwnd(), hAccel, pMsg);
}

(2) 仮想関数 PreTranslateMessage をオーバーライドして先述の関数を呼ぶ

BOOL CTestDlg::PreTranslateMessage(MSG* pMsg)
{
   if (TranslateAccelerator(pMsg))
       return TRUE;
   return CDialog::PreTranslateMessage(pMsg);
}

以上で WM_COMMAND (OnCommand) で受けられるように成ります.

または,次のようにして RegisterHotKey を利用しては如何でしょうか.
この場合は アクセラレータ & OnCommand は使用しません.

(1) 適当に ID を設定

const int ID_TEST = 100;

(2) 開始時に RegisterHotKey

BOOL CTestDlg::OnInitDialog()
{
   CDialog::OnInitDialog();
   ::RegisterHotKey(GetSafeHwnd(), ID_TEST, MOD_CONTROL, UINT('D'));
}

(3) 終了時に UnregisterHotKey

void CTestDlg::OnDestroy()
{
   ::UnregisterHotKey(GetSafeHwnd(), ID_TEST);
   CDialog::OnDestroy();
}

(4) メッセージ ハンドラ (for WM_HOTKEY) を追加

BEGIN_MESSAGE_MAP(CTestDlg, CDialog)
//{{AFX_MSG_MAP(CTestDlg)
   ON_MESSAGE(WM_HOTKEY, OnHotKey)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

LRESULT CTestDlg::OnHotKey(WPARAM wParam, LPARAM lParam)
{
   if (int(wParam) == ID_TEST &&
       UINT(LOWORD(lParam)) == MOD_CONTROL && UINT(HIWORD(lParam)) == UINT('D')) {
       // 目的の処理を行う
   }
   return LRESULT(0);
}

【情報】 ドキュメント-ビュー構造のクラス図 (MDI)

MFC でドキュメント-ビュー構造 (MDI) の場合のクラス図 (UML) を描いてみました.
Microsoft Visual Modeler を使用しています.

MFC クラス図

【Tips】 CView::OnUpdate の CObject のラッパー クラス

CDocument の更新を CView に通知するには CDocument::UpdateAllViews と CView::OnUpdate を使うのが普通です (Observer パターン) が,この時ユーザー定義の更新データを渡すことが出来ます (Observer パターンの push モデル).

ところが,この時受け渡すのは CObject へのポインタと成っています.CObject から派生していないオブジェクトを渡すのには不便です.
例えば,再描画の為の更新領域を CRect で渡そうとしても CRect は CObject ではないので渡しづらいのです.

   virtual void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL);
   virtual void CView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);

そこで,CObject から派生していないクラスのオブジェクトを渡す為のラッパー クラスを作成してみました.

//---------------------------------------------------------------------------
//  CObjectWrapper.h

#ifndef CObjectWrapper_h
#define CObjectWrapper_h

#include <afxwin.h>


template <class Type>
class CObjectWrapper : public CObject
{
public:
   explicit CObjectWrapper(const Type& object)
   : wrapped_(object)
   {}

   operator const Type&() const
   { return wrapped_; }

private:
   Type    wrapped_;
};


#endif  //  CObjectWrapper_h

//---------------------------------------------------------------------------
// CObjectWrapperTestView.h

#ifndef CObjectWrapperTestView_h
#define CObjectWrapperTestView_h

#include "CObjectWrapper.h"


class CCObjectWrapperTestView : public CView
{
   DECLARE_DYNCREATE(CCObjectWrapperTestView)

   virtual void OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint);
   virtual void OnDraw(CDC*) {}

   afx_msg void OnTest();
   DECLARE_MESSAGE_MAP()

   enum // for lHint
   {
       UPDATE_RECT = 1
   };

   // Wrapper
   typedef CObjectWrapper<CRect> Wrapper;

   static void TestResult(const CRect& updateRect);
};


#endif  //  CObjectWrapperTestView_h

//---------------------------------------------------------------------------
//  CObjectWrapperTestView.cpp

#include "CObjectWrapperTestView.h"
#include "resource.h" // for ID_TEST


IMPLEMENT_DYNCREATE(CCObjectWrapperTestView, CView)

BEGIN_MESSAGE_MAP(CCObjectWrapperTestView, CView)
   ON_COMMAND(ID_TEST, OnTest)
END_MESSAGE_MAP()

void CCObjectWrapperTestView::OnUpdate(CView* /* pSender */,
                                      LPARAM lHint, CObject* pHint)
{
   switch (lHint) {
       case UPDATE_RECT:
           TestResult(*(Wrapper*)pHint);
           break;
   }
}

void CCObjectWrapperTestView::OnTest()
{
   const CRect updateRect(10, 20, 30, 40); // Dummy Data for Test

   // 他のビューへ更新領域を通知するテスト
   GetDocument()->UpdateAllViews(this, UPDATE_RECT,
                                 &Wrapper(updateRect));
}

void CCObjectWrapperTestView::TestResult(const CRect& updateRect)
{
   // 結果の確認
   TRACE(_T("CCObjectWrapperTestView::TestResult(%d, %d, %d, %d)\n"),
         updateRect.left , updateRect.top   ,
         updateRect.right, updateRect.bottom);
}

【Tips】 MFC (または C++ 関数) と API の比較

MFC と C++ の標準関数 と Windows API には良く似た機能のものが有ります.一部を表にしてみます.

MFC (または C++ 関数) Windows API
ウィンドウ CWnd とその派生クラス <<クラス>> CreateWindow 等 <<関数>>
GDI リソース CGdiObject とその派生クラス <<クラス>> CreatePen 等 <<関数>>
デバイス コンテキスト CDC とその派生クラス <<クラス>> BeginPaint, GetDC 等 <<関数>>
文字列操作 CString <<クラス>>(_tcscpy, _tcslen, _tcscmp 等 <<関数>>) lstrcpy, lstrlen, lstrcmp 等 <<関数>>
メモリー操作 コンストラクタ <<クラス>> (memset, memcpy 等 <<関数>>) ZeroMemory, FillMemory, CopyMemory, MoveMemory 等 <<関数>>

【Tips】 MFC と C++ 及び C++ 標準ライブラリの比較

MFC と C++ でも良く似た機能のものが有ります.元々は,C++ の方が標準的なコレクション クラスや型情報,キャストの機能を持っていなかった時期に MFC の方でサポートされたものです.
一部を表にしてみます.

・コレクション クラス等 <<テンプレート クラス>>

MFC C++ 標準
配列 CArray 等 vector
リスト CList 等 list
マップ CMap 等 map
文字列 CString string

・型情報クラスとキャスト

DECLARE_DYNAMIC + IMPLEMENT_DYNAMIC <<マクロ>>
MFC C++ 標準
型情報型 COjbect + CRuntimeClass <<クラス>> type_info <<クラス>>
型情報生成 RUNTIME_CLASS <<マクロ>> typeid <<演算子>>
静的キャスト STATIC_DOWNCAST <<マクロ>> static_cast, const_cast, reinterpret_cast <<演算子>>
動的キャスト DYNAMIC_DOWNCAST <<マクロ>> dynamic_cast <<演算子>>
必要なオプション

・例外処理

MFC C++ 標準
Try TRY <<マクロ>> try <<キーワード>>
Catch CATCH, AND_CATCH, END_CATCH, END_CATCH_ALL <<マクロ>> catch <<キーワード>>
Throw THROW, THROW_LAST <<マクロ>> throw <<キーワード>>

【Q&A】 デバッガにテキストを出力したい

【Q】 デバッグ時に,Microsoft Developer Studio 等のデバッガに文字を出力したいのですが.

【A】 MFC を使用している場合は,TRACE マクロを使用します.MFC を使っていない場合は,_RPT0 若しくは _RPT1,_RPT2,_RPT3,_RPT4 等のマクロが使えます.

例.

#include <tchar.h>
#include <crtdbg.h> /* _RPT0,_RPT1,_RPT2,_RPT3,_RPT4 には必要 */

/* デバッグ出力                                                    */
/* Just-In-Time (JIT) デバッグが有効な場合はデバッガに文字列を出力 */
/* (C/C++ 用,Microsoft Visual C++ 依存)                           */
void Trace(const TCHAR* messageText /* デバッグ出力する文字 */)
{
   _RPT0(_CRT_WARN, messageText);
}


【Q&A】 MFCを使用していない VC++ アプリでのメモリ リークの検出方法

【Q】 MFC を使用していないアプリケーションのデバッグ時に,メモリ リークを検出したいのですが.

【A】 crtdbg.h というヘッダー ファイルをインクルードし,プログラムの終了時に _CrtDumpMemoryLeaks 関数 を呼ぶか,プログラムの開始時に _CrtSetDbgFlag 関数を使用して _CRTDBG_LEAK_CHECK_DF フラグをオンにしておきます.
すると,アプリケーション終了時にメモリ リークが存在すれば,VC++ の「アウトプット」の「デバッグ」ウィンドウに,ソースのファイル名と行番号付きでダンプされます.

例.

// MemoryLeakTest.h
#ifndef MemoryLeakTest_h
#define MemoryLeakTest_h

#ifdef  _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#endif  // _DEBUG

#ifndef DEBUG_NEW
#ifdef  _DEBUG
#define DEBUG_NEW   ::new(_NORMAL_BLOCK, __FILE__, __LINE__)
#else   // _DEBUG
#define DEBUG_NEW   new
#endif
 // _DEBUG
#endif  // DEBUG_NEW

#define new DEBUG_NEW

// アプリケーションの開始時に呼ぶ
inline void CheckMemoryLeaksStart()
{
#ifdef  _DEBUG
   _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_LEAK_CHECK_DF);
#endif  // _DEBUG
}

#endif  // MemoryLeakTest_h

// Main.cpp
#include <iostream>
// 各ソースやヘッダーで "MemoryLeakTest.h" をインクルード
#include "MemoryLeakTest.h"

int Sub();

int main()
{
   CheckMemoryLeaksStart();

   // …省略…
   Sub();
   // …省略…

   return 0;
}

// Sub.cpp
#include <iostream>
// 各ソースやヘッダーで "MemoryLeakTest.h" をインクルード
#include "MemoryLeakTest.h"

int Sub()
{
   // …省略…
}