カテゴリー
C++

アプリケーション クラッシュ時のダンプ自動取得

Windows Vista SP1 以降 アプリケーションがクラッシュしたときのダンプを手動で取得する方法は情報が結構見つかりますが、自動で取得する方法はあまり知られていないようなので紹介します。

下記レジストリにキーと値を追加します。デフォルトでは無効となっています。(=ダンプの自動取得は無効)

また、特定のアプリケーションごとに設定することもできます。ここでは、グローバルな設定のみご紹介します。

HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsWindows Error ReportingLocalDumps

説明 種類 デフォルト値
DumpFolder ダンプファイルを生成するフォルダを指定します。例)c:dump REG_EXPAND_SZ %LOCALAPPDATA%CrashDumps
DumpCount フォルダ内のダンプファイルの最大数。最大数に達すると古いものが新しいものに置換されます。 REG_DWORD 10
DumpType ダンプの種類:

  • 0: カスタム ダンプ
  • 1: ミニ ダンプ
  • 2: 完全ダンプ
REG_DWORD 1
CustomDumpFlags カスタムダンプのオプション。DumpType が 0 の時に使用 REG_DWORD MiniDumpWithDataSegs | MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData.
(※ここはよくわからないので 0 をセット)

参考:https://msdn.microsoft.com/en-us/library/bb787181.aspx

こちらが私がレジストリエディタでセットした値です。参考までに。。。

ローカルダンプのレジストリ設定
ローカルダンプのレジストリ設定
カテゴリー
C++

Windowsの特別なフォルダ(SHFolders)の列挙

Windows XP, Windows Vista, Windows 7 で動作するアプリを作成する上で、アプリ用のデータを保存するフォルダーとか、個人用のデータを保存するフォルダーなどを Windows 既定の場所を使用したいと思い、そのフォルダーの取得方法を調べました。

MSDNによると、CSIDL を使用する方法はWindows Vista 以降は、互換性のために残されていて、今後は、KNOWNFOLDERID を使用するようにとのことです。

CSIDL:https://msdn.microsoft.com/en-us/library/bb762494%28v=vs.85%29.aspx
KNOWNFOLDERID: https://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx

CSIDL の取得

CSIDL の値をすべて配列にいれておいて、ループで実際のフォルダー名を列挙してます。

// SHFolders.cpp : Known Folders の列挙 
//

#include "stdafx.h"
#include <shlobj.h>


int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR szPath[MAX_PATH];
    typedef struct {
        int csidl;
        TCHAR szId[50];
    } SHFOLDER;

    // CSIDL https://msdn.microsoft.com/en-us/library/bb762494%28v=vs.85%29.aspx

    SHFOLDER folders[] = {
        { CSIDL_ADMINTOOLS, _T("CSIDL_ADMINTOOLS") },
        { CSIDL_ALTSTARTUP, _T("CSIDL_ALTSTARTUP") },
        { CSIDL_APPDATA, _T("CSIDL_APPDATA") },
        { CSIDL_BITBUCKET, _T("CSIDL_BITBUCKET") },
        { CSIDL_CDBURN_AREA, _T("CSIDL_CDBURN_AREA") },
        { CSIDL_COMMON_ADMINTOOLS, _T("CSIDL_COMMON_ADMINTOOLS") },
        { CSIDL_COMMON_ALTSTARTUP, _T("CSIDL_COMMON_ALTSTARTUP") },
        { CSIDL_COMMON_APPDATA, _T("CSIDL_COMMON_APPDATA") },
        { CSIDL_COMMON_DESKTOPDIRECTORY, _T("CSIDL_COMMON_DESKTOPDIRECTORY") },
        { CSIDL_COMMON_DOCUMENTS, _T("CSIDL_COMMON_DOCUMENTS") },
        { CSIDL_COMMON_FAVORITES, _T("CSIDL_COMMON_FAVORITES") },
        { CSIDL_COMMON_MUSIC, _T("CSIDL_COMMON_MUSIC") },
        { CSIDL_COMMON_OEM_LINKS, _T("CSIDL_COMMON_OEM_LINKS") },
        { CSIDL_COMMON_PICTURES, _T("CSIDL_COMMON_PICTURES") },
        { CSIDL_COMMON_PROGRAMS, _T("CSIDL_COMMON_PROGRAMS") },
        { CSIDL_COMMON_STARTMENU, _T("CSIDL_COMMON_STARTMENU") },
        { CSIDL_COMMON_STARTUP, _T("CSIDL_COMMON_STARTUP") },
        { CSIDL_COMMON_TEMPLATES, _T("CSIDL_COMMON_TEMPLATES") },
        { CSIDL_COMMON_VIDEO, _T("CSIDL_COMMON_VIDEO") },
        { CSIDL_COMPUTERSNEARME, _T("CSIDL_COMPUTERSNEARME") },
        { CSIDL_CONNECTIONS, _T("CSIDL_CONNECTIONS") },
        { CSIDL_CONTROLS, _T("CSIDL_CONTROLS") },
        { CSIDL_COOKIES, _T("CSIDL_COOKIES") },
        { CSIDL_DESKTOP, _T("CSIDL_DESKTOP") },
        { CSIDL_DESKTOPDIRECTORY, _T("CSIDL_DESKTOPDIRECTORY") },
        { CSIDL_DRIVES, _T("CSIDL_DRIVES") },
        { CSIDL_FAVORITES, _T("CSIDL_FAVORITES") },
        { CSIDL_FONTS, _T("CSIDL_FONTS") },
        { CSIDL_HISTORY, _T("CSIDL_HISTORY") },
        { CSIDL_INTERNET, _T("CSIDL_INTERNET") },
        { CSIDL_INTERNET_CACHE, _T("CSIDL_INTERNET_CACHE") },
        { CSIDL_LOCAL_APPDATA, _T("CSIDL_LOCAL_APPDATA") },
        { CSIDL_MYDOCUMENTS, _T("CSIDL_MYDOCUMENTS") },
        { CSIDL_MYMUSIC, _T("CSIDL_MYMUSIC") },
        { CSIDL_MYPICTURES, _T("CSIDL_MYPICTURES") },
        { CSIDL_MYVIDEO, _T("CSIDL_MYVIDEO") },
        { CSIDL_NETHOOD, _T("CSIDL_NETHOOD") },
        { CSIDL_NETWORK, _T("CSIDL_NETWORK") },
        { CSIDL_PERSONAL, _T("CSIDL_PERSONAL") },
        { CSIDL_PRINTERS, _T("CSIDL_PRINTERS") },
        { CSIDL_PRINTHOOD, _T("CSIDL_PRINTHOOD") },
        { CSIDL_PROFILE, _T("CSIDL_PROFILE") },
        { CSIDL_PROGRAM_FILES, _T("CSIDL_PROGRAM_FILES") },
        { CSIDL_PROGRAM_FILESX86, _T("CSIDL_PROGRAM_FILESX86") },
        { CSIDL_PROGRAM_FILES_COMMON, _T("CSIDL_PROGRAM_FILES_COMMON") },
        { CSIDL_PROGRAM_FILES_COMMONX86, _T("CSIDL_PROGRAM_FILES_COMMONX86") },
        { CSIDL_PROGRAMS, _T("CSIDL_PROGRAMS") },
        { CSIDL_RECENT, _T("CSIDL_RECENT") },
        { CSIDL_RESOURCES, _T("CSIDL_RESOURCES") },
        { CSIDL_RESOURCES_LOCALIZED, _T("CSIDL_RESOURCES_LOCALIZED") },
        { CSIDL_SENDTO, _T("CSIDL_SENDTO") },
        { CSIDL_STARTMENU, _T("CSIDL_STARTMENU") },
        { CSIDL_STARTUP, _T("CSIDL_STARTUP") },
        { CSIDL_SYSTEM, _T("CSIDL_SYSTEM") },
        { CSIDL_SYSTEMX86, _T("CSIDL_SYSTEMX86") },
        { CSIDL_TEMPLATES, _T("CSIDL_TEMPLATES") },
        { CSIDL_WINDOWS, _T("CSIDL_WINDOWS") }
    };

    // フォルダーの列挙
    for (int i = 0; i < _countof(folders); i++)
    {
        if (SUCCEEDED(SHGetFolderPath(NULL, 
                                       folders[i].csidl | CSIDL_FLAG_DONT_VERIFY, 
                                       NULL, 
                                      0, 
                                      szPath))) 
        {
            _tprintf(_T("%s:%sn"), folders[i].szId, szPath);
        }
    }

    return 0;
}

特別なフォルダの列挙

上記プログラムの実行結果は以下のとおりです。(フォルダ名の一部に個人名が使用されているところは編集しました。)

CSIDL_ADMINTOOLS:C:Documents and Settingsスタート メニュープログラム管理ツール
CSIDL_ALTSTARTUP:
CSIDL_APPDATA:C:Documents and SettingsApplication Data
CSIDL_CDBURN_AREA:C:Documents and SettingsLocal SettingsApplication DataMicrosoftCD Burning
CSIDL_COMMON_ADMINTOOLS:C:Documents and SettingsAll Usersスタート メニュープログラム管理ツール
CSIDL_COMMON_ALTSTARTUP:
CSIDL_COMMON_APPDATA:C:Documents and SettingsAll UsersApplication Data
CSIDL_COMMON_DESKTOPDIRECTORY:C:Documents and SettingsAll Usersデスクトップ
CSIDL_COMMON_DOCUMENTS:C:Documents and SettingsAll UsersDocuments
CSIDL_COMMON_FAVORITES:C:Documents and SettingsAll UsersFavorites
CSIDL_COMMON_MUSIC:C:Documents and SettingsAll UsersDocumentsMy Music
CSIDL_COMMON_OEM_LINKS:
CSIDL_COMMON_PICTURES:C:Documents and SettingsAll UsersDocumentsMy Pictures
CSIDL_COMMON_PROGRAMS:C:Documents and SettingsAll Usersスタート メニュープログラム
CSIDL_COMMON_STARTMENU:C:Documents and SettingsAll Usersスタート メニュー
CSIDL_COMMON_STARTUP:C:Documents and SettingsAll Usersスタート メニュープログラムスタートアップ
CSIDL_COMMON_TEMPLATES:C:Documents and SettingsAll UsersTemplates
CSIDL_COMMON_VIDEO:C:Documents and SettingsAll UsersDocumentsMy Videos
CSIDL_COOKIES:C:Documents and SettingsCookies
CSIDL_DESKTOP:C:Documents and Settingsデスクトップ
CSIDL_DESKTOPDIRECTORY:C:Documents and Settingsデスクトップ
CSIDL_FAVORITES:C:Documents and SettingsFavorites
CSIDL_FONTS:C:WINDOWSFonts
CSIDL_HISTORY:C:Documents and SettingsLocal SettingsHistory
CSIDL_INTERNET_CACHE:C:Documents and SettingsLocal SettingsTemporary Internet Files
CSIDL_LOCAL_APPDATA:C:Documents and SettingsLocal SettingsApplication Data
CSIDL_MYMUSIC:C:Documents and SettingsMy DocumentsMy Music
CSIDL_MYPICTURES:C:Documents and SettingsMy DocumentsMy Pictures
CSIDL_MYVIDEO:C:Documents and SettingsMy DocumentsMy Videos
CSIDL_NETHOOD:C:Documents and SettingsNetHood
CSIDL_PERSONAL:C:Documents and SettingsMy Documents
CSIDL_PRINTHOOD:C:Documents and SettingsPrintHood
CSIDL_PROFILE:C:Documents and Settings
CSIDL_PROGRAM_FILES:C:Program Files
CSIDL_PROGRAM_FILES_COMMON:C:Program FilesCommon Files
CSIDL_PROGRAMS:C:Documents and Settingsスタート メニュープログラム
CSIDL_RECENT:C:Documents and SettingsRecent
CSIDL_RESOURCES:C:WINDOWSresources
CSIDL_RESOURCES_LOCALIZED:C:WINDOWSresources411
CSIDL_SENDTO:C:Documents and SettingsSendTo
CSIDL_STARTMENU:C:Documents and Settingsスタート メニュー
CSIDL_STARTUP:C:Documents and Settingsスタート メニュープログラムスタートアップ
CSIDL_SYSTEM:C:WINDOWSsystem32
CSIDL_SYSTEMX86:C:WINDOWSsystem32
CSIDL_TEMPLATES:C:Documents and SettingsTemplates
CSIDL_WINDOWS:C:WINDOWS

以上です。

カテゴリー
C++

Windows アプリケーション タスクトレイ表示

Windows アプリケーションのメインウィンドウを表示しないで、タスクトレイ(下記画像)へアイコンとして表示しておいて、マウスでクリックしたら何か表示して、処理をするというのをやってみたいと思います。
(※呼び方がタスクトレイで良いのかわかりませんが、タスクバーの通知領域またはシステムトレイとも言われるようです。)

タスクトレイ アイコン
タスクトレイ アイコン

手順として:
1.Visual Studio で Windows ダイアログ アプリケーションを作ります
2.起動時にアイコンをタスクトレイへ表示します。
3.メインウィンドウを非表示にします。
4.タスクトレイのアイコンがクリックされたらメインウィンドウを表示します。
5.終了時にアイコンをタスクトレイから削除

といった感じで作成します。 なお、1については割愛します。

起動時にアイコンをタスクトレイへ表示

アイコンをタスクトレイへ表示するには、Shell_NotifyIcon API で簡単にできます。パラメータは「追加」、「変更」、「削除」があり、ウィンドウの作成時に追加して、クローズ時に削除すればよいです。

Shell_NotifyIcon を使うために便利な関数を自分のメインウィンドウのクラスに用意してみました。(下記参照)
アイコンなどのリソースはあらかじめ作成しておきます。ここでは、 IDR_TRAYICON というアイコンがある前提です。

MYWM_TRAYNOTIFY というメッセージID を nid.uCallbackMessage に設定してます。これは WM_USER + 1 で定義したユーザー定義メッセージで、タスクトレイのアイコンがクリックされたらメインウィンドウに通知されてきます。

... (省略) ...

#define MYWM_TRAYNOTIFY        WM_USER + 1

... (省略) ...

//
// TrayNotifyIconMessage
//
// dwMessage:
//        NIM_ADD
//        NIM_MODIFY
//        NIM_DELETE
//
BOOL CMyDialog::TrayNotifyIconMessage(DWORD dwMessage)
{
    CString sTip = _T("ポイントされたら表示するテキストを指定します");
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd   = GetSafeHwnd();
    nid.uID    = IDR_TRAYICON;        // <-- アイコンIDを指定してます
    nid.uFlags = NIF_MESSAGE | NIF_ICON;
    nid.uCallbackMessage = MYWM_TRAYNOTIFY;    // <-- WM_USER + 1 とかでメッセージ定義
    nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
    nid.hIcon  = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDR_TRAYICON));
    _tcscpy_s(nid.szTip, _countof(nid.szTip), (LPCTSTR)sTip);
    
    return Shell_NotifyIcon(dwMessage, &nid);
}

起動時にアイコンをタスクトレイへ追加します。ダイアログアプリケーションでは OnInitDialog 内で上記の TrayNotifyIconMessage を NIM_ADD をパラメータで指定するだけです。これで、起動時にタスクトレイにアイコンが表示されます。

BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

... (省略) ...

    // タスクトレイへアイコンを追加
    TrayNotifyIconMessage(NIM_ADD);    

    return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

メインウィンドウを非表示にする

アプリケーション最小化時にタスクトレイに表示させて、タスクバーには表示されないようにするようなので、それを実現するために、メインウィンドウを最小化時に非表示にします。(もちろん、起動時に非表示にしてしまうこともできますが。。。)

WM_SIZE のメッセージハンドラ OnSize 内でパラメータの nType が SIZE_MINIMIZED だった時に ShowWIndow で非表示にします。

void CMyDialog::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);

    if (nType == SIZE_MINIMIZED)
    {
        ShowWindow(SW_HIDE);
    }
}

このままだと、二度とメインウィンドウが表示されることがないので、タスクトレイのアイコンがダブルクリックされたらメインウィンドウを再表示するようにします。

タスクトレイのアイコンがクリックされたらメインウィンドウを表示

まずは、最初に定義済みの MYWM_TRAYNOTIFY というユーザー定義メッセージをハンドルする OnTrayNotify 関数を用意します。

パラメータの lParam にイベントの種類が入ってきますので、ダブルクリックされたのを判定する WM_LBUTTONDBLCLK と自アプリケーションのアイコンかを判定し、処理を行います。

ここでは、メインウィンドウを表示してフォアグラウンドに持ってきます。

... (省略) ...

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    //}}AFX_MSG_MAP
    ON_WM_SIZE()
    ON_WM_CLOSE()
    ON_MESSAGE(MYWM_TRAYNOTIFY, OnTrayNotify)
END_MESSAGE_MAP()

... (省略) ...

LRESULT CMyDialog::OnTrayNotify(WPARAM wParam, LPARAM lParam)
{
    switch (lParam)
    {
    case WM_LBUTTONDBLCLK: 
        if (wParam == IDR_TRAYICON)
        {
            ShowWindow(SW_NORMAL);
            SetForegroundWindow();
            SetFocus();
        } 
        break;

    default:
        break;
    } 

    return 0;
}

終了時の処理

そして、最後にメインアプリケーションがクローズされるときに、タスクトレイのアイコンを削除します。

void CMyDialog::OnClose()
{
    // タスクトレイからアイコンを削除
    TrayNotifyIconMessage(NIM_DELETE);    

    CDialog::OnClose();
}

以上です。

あとは、必要に応じて、ポップアップメニューを表示したりする処理を OnTrayNotify 内に追加するなど応用できます。

カテゴリー
C++

Visual C++/MFCライブラリ CSVカンマ区切りの処理

Visual C++ と MFC ライブラリは長い間使っているけど、他の言語のライブラリにはカンマ区切りの文字列を一発で配列に収めて返してくれる split 関数なるものが普通にあります。あらためて MFC CString にはそれがないことを認識しました。むかし、そのような処理をやったことがあったので、そのコードを抜粋しました。

以下、コード抜粋

    CString str = "111,222,333,444,555,,aaa,bbb,ccc,ddd,eee,fff";
    CArray<CString,CString> arr;

    // カンマ区切りを配列に格納
    arr.RemoveAll();
        
    int iStart = 0;
    int iPos = -1;

    CString restoken;
    iPos = str.Find(',', iStart);

    while(iPos > -1)
    {
        restoken = str.Mid(iStart, iPos - iStart);
        arr.Add(restoken);
        printf("Resulting token: %sn", restoken);

        iStart = iPos + 1;
        iPos = str.Find(',', iStart);

        if (iPos == -1)
        {
            restoken = str.Mid(iStart);
            arr.Add(restoken);
            printf("Resulting token: %sn", restoken);
        }
    }

出力結果

Resulting token: 111
Resulting token: 222
Resulting token: 333
Resulting token: 444
Resulting token: 555
Resulting token:
Resulting token: aaa
Resulting token: bbb
Resulting token: ccc
Resulting token: ddd
Resulting token: eee
Resulting token: fff

CSVファイルによっては、各列データををダブルクォーテーションで囲む場合もあるので、それは対応していません。;-)

また、MSDN CStringT::Tokenize のサンプル トークンの区切り文字をカンマにすると良さそうです。
※ 但し、カンマとカンマに文字が何もない場合(赤字の部分)は、その列は無視して次の列をトークンとして読み込むようです。CSV ファイルの処理で、空き列も処理する必要がある場合は要注意です。

以下、MSDN サンプルを一部変更して

    CAtlString str( "111,222,333,444,555,,aaa,bbb,ccc,ddd,eee,fff" );
    CAtlString resToken;
    int curPos= 0;

    resToken= str.Tokenize(",",curPos);
    while (resToken != "")
    {
        printf("Resulting token: %sn", resToken);
        resToken= str.Tokenize(",",curPos);
    }

出力結果

Resulting token: 111
Resulting token: 222
Resulting token: 333
Resulting token: 444
Resulting token: 555
Resulting token: aaa
Resulting token: bbb
Resulting token: ccc
Resulting token: ddd
Resulting token: eee
Resulting token: fff