質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.76%

おそらくリソース不足と思われますが、アプリが止まってしまいます。Visual Studio 2019 C++

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 688

背景

Windows10 Visual Studio 2019 C++ でアプリを作成しようと考えています。Visual Studio を使い始めてから、まだ 1か月程度の初心者です。
サンプル画像を表示し、そのサンプル画像の上にいくつかの「ワク」を表示させ、マウスで移動させたり、大きさを変化させたりしたいと考えています。
実際のプログラム( cpp ファィル )は、以下に引用するとおりですが、動いている様子については、次の Youtube の動画のようになっています。
⇒ Visual Studio 2019 C++ アプリが途中で止まる。

エラーメッセージ

エラーメッセージは、表示されていません。
しかし、Youtube の動画のとおり、途中で動きが止まってしまいます。

プログラムソース

cppファイルの内容をまるごと以下に引用します。

#include "framework.h"
#include "ScrollBar_Waku.h"
#include "strconv.h"
#include "atlimage.h"
#include <string>
using namespace std;

#define MAX_LOADSTRING 100
#define MAX_WAKU 100

// グローバル変数:
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];

static CString filePath;
static CImage img;
static int width, height, myTop, mousex, mousey, memx, memy, mouseMode;
static SCROLLINFO si;

static int nowWaku, maxWaku;
static RECT tmpWaku;
static vector <RECT> myWaku(MAX_WAKU);

void InitWaku() { // テスト用にワクを設定しておく(将来的には削除)
    mouseMode = MODE_DONE;
    nowWaku = 0;
    maxWaku = 10;
    for (int i = 0; i < maxWaku; i++) {
        myWaku.at(i).top    = 10 + i*50;    myWaku.at(i).left = 50;
        myWaku.at(i).bottom = 50 + i*50;    myWaku.at(i).right = 350;
    }
}

// このコード モジュールに含まれる関数の宣言を転送します:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int        nCmdShow) {
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: ここにコードを挿入してください。
    filePath = L"sample.png";
    img.Load(filePath);
    width = img.GetWidth();
    height = img.GetHeight();
    mousex = 0;
    mousey = 0;
    InitWaku();

    // グローバル文字列を初期化する
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_SCROLLBARWAKU, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // アプリケーション初期化の実行:
    if (!InitInstance(hInstance, nCmdShow)) { return FALSE; }
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SCROLLBARWAKU));
    MSG msg;

    // メイン メッセージ ループ:
    while (GetMessage(&msg, nullptr, 0, 0)) {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

//    目的: ウィンドウ クラスを登録します。
ATOM MyRegisterClass(HINSTANCE hInstance) {
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SCROLLBARWAKU));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_SCROLLBARWAKU);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcex);
}

//    目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) {
    hInst = hInstance;
    HWND hWnd = CreateWindowW(szWindowClass, szTitle,
        WS_OVERLAPPEDWINDOW | WS_VSCROLL, // ウインドウの種類
        50, // x座標
        50, // y座標
        800,// 幅
        600,// 高さ
        nullptr,    // 親ウインドのハンドル
        nullptr,    // メニューハンドル nullptr はクラスメニュー
        hInstance,
        nullptr
    );
    if (!hWnd) { return FALSE; }
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

int CheckMouse() {
    int tmpwk = nowWaku;
    mouseMode = MODE_DONE;
    for (int i = 0; i < maxWaku; i++) {
        if (    (memy > myWaku.at(i).top - myTop) 
            &&    (memy < myWaku.at(i).bottom - myTop)
            &&    (memx > myWaku.at(i).left)
            &&    (memx < myWaku.at(i).right)) {
            tmpwk = i;
            mouseMode = MODE_WAKU1;
            break;
        }
    }
    return tmpwk;
}

void DrawCursor(HDC mem) {
    COLORREF mycolor;
    mycolor = RGB(0xff, 0x00, 0x00);
    HPEN hpen = CreatePen(PS_SOLID, 5, mycolor);
    HGDIOBJ ohpen = SelectObject(mem, hpen);
    SelectObject(mem, GetStockObject(NULL_BRUSH));
    Ellipse(mem, memx - 15, myTop + memy - 15, memx + 15, myTop + memy + 15);
    SelectObject(mem, ohpen);
    DeleteObject(hpen);
}

void DrawWaku(HDC mem,int wk) {
    COLORREF mycolor;
    if (wk == nowWaku) {
        mycolor = RGB(0xff, 0x00, 0x00);
    }
    else {
        mycolor = RGB(0x00, 0x00, 0xff);
    }
    HPEN hpen = CreatePen(PS_SOLID, 5, mycolor);
    SelectObject(mem, GetStockObject(NULL_BRUSH));
    HGDIOBJ ohpen = SelectObject(mem, hpen);
    Rectangle(mem, myWaku.at(wk).left, myWaku.at(wk).top, myWaku.at(wk).right, myWaku.at(wk).bottom);
    SelectObject(mem, ohpen);
    DeleteObject(hpen);
}

//    目的: メイン ウィンドウのメッセージを処理します。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {

    RECT recDisp;
    GetClientRect(hWnd, &recDisp);    // クライアントを取得
    int cx = recDisp.right;            // クライアントのヨコ
    int cy = recDisp.bottom;        // クライアントのタテ
    int tate;                        // イメージのタテ
    if (cx == 0) {
        tate = 0;  memx = 0; memy = 0;
    } else {
        tate = cy * width / cx; memx = mousex * width / cx; memy = mousey * width / cx;
    }

    switch (message) {

    case WM_PAINT: {
        // ここから実際の描写
        HDC hdc = GetDC(hWnd);
        HDC mem = CreateCompatibleDC(hdc);
        HBITMAP bmp = CreateCompatibleBitmap(hdc, width, height);
        HGDIOBJ obmp = SelectObject(mem, bmp);

        img.Draw(mem, 0, 0, width, height, 0, 0, width, height);
        for (int i = 0; i < maxWaku; i++) { DrawWaku(mem, i); }
        DrawCursor(mem);

        nowWaku = CheckMouse();
        wstring wmouseMode;
        switch (mouseMode) {
            case MODE_DONE:  wmouseMode = L"MODE_DONE"; break;
            case MODE_CLICK: wmouseMode = L"MODE_CLICK"; break;
            case MODE_WAKU1: wmouseMode = L"MODE_WAKU1"; break;
            case MODE_WAKU2: wmouseMode = L"MODE_WAKU2"; break;
            case MODE_END1:  wmouseMode = L"MODE_END1"; break;
            case MODE_END2:  wmouseMode = L"MODE_END2"; break;
            default:         wmouseMode = L"default"; break;
        }

        PAINTSTRUCT ps;
        BeginPaint(hWnd, &ps);
        StretchBlt(hdc, 0, 0, cx, cy, mem, 0, myTop, width, tate, SRCCOPY);
        TextOut(hdc, 200, 50, wmouseMode.c_str(), (int)wmouseMode.length());
        EndPaint(hWnd, &ps);

        SelectObject(mem, obmp);
        DeleteObject(bmp);

        si.cbSize = sizeof(SCROLLINFO);
        si.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
        si.nMin = 0;
        si.nMax = height - tate;
        si.nPos = myTop;
        SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
        } break;

    case WM_VSCROLL: {
        switch (LOWORD(wParam)) {
            case SB_LINEUP:
                if (myTop > 10) { myTop -= 10; } else { myTop = 0; } break;
            case SB_LINEDOWN:
                if (myTop < height - tate - 10) { myTop += 10; } else { myTop = height - tate; } break;
            case SB_PAGEUP:
                if (myTop > tate) { myTop -= tate; } else { myTop = 0; } break;
            case SB_PAGEDOWN:
                if (myTop < height - tate * 2) { myTop += tate; } else { myTop = height - tate; } break;
            case SB_THUMBTRACK:
                myTop = HIWORD(wParam); break;
            }
        InvalidateRect(hWnd, NULL, FALSE);
        } break;

    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_MOUSEMOVE:
        mousex = LOWORD(lParam);
        mousey = HIWORD(lParam);
        InvalidateRect(hWnd, NULL, FALSE);
        break;

    case WM_KEYDOWN: {
        switch (wParam) {
            case VK_ESCAPE:
                if (maxWaku == 0) {
                    PostQuitMessage(0);
                } else {
                    maxWaku--;
                    nowWaku = maxWaku-1;
                }
                break;
            case VK_NEXT:
                if (myTop < height - tate * 2) { myTop += tate; }
                else { myTop = height - tate; } break;
            case VK_PRIOR:
                if (myTop > tate) { myTop -= tate; }
                else { myTop = 0; } break;
            case VK_DOWN:
                if (myTop < height - tate - 10) { myTop += 10; } break;
            case VK_UP:
                if (myTop > 10) { myTop -= 10; } break;
            }
        InvalidateRect(hWnd, NULL, FALSE);
        } break;

    case WM_COMMAND: {
        int wmId = LOWORD(wParam);
        // 選択されたメニューの解析:
        switch (wmId) {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        } break;
    case WM_DESTROY: PostQuitMessage(0); break;
    default: return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// バージョン情報ボックス
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
    UNREFERENCED_PARAMETER(lParam);
    switch (message) {
    case WM_INITDIALOG: return (INT_PTR)TRUE;
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        } break;
    }
    return (INT_PTR)FALSE;
}

リソース不足が原因でしょうか

「途中で止まってしまう」という現象から考えて、リソースの開放に失敗しているのではないかと予想しています。しかし該当すると思われるところについては、以下のとおり DeleteObject で開放していると思うのですが、間違っていますでしょうか。

    HGDIOBJ ohpen = SelectObject(mem, hpen);
    SelectObject(mem, GetStockObject(NULL_BRUSH));
    Ellipse(mem, memx - 15, myTop + memy - 15, memx + 15, myTop + memy + 15);
    SelectObject(mem, ohpen);
    DeleteObject(hpen);
    HPEN hpen = CreatePen(PS_SOLID, 5, mycolor);
    SelectObject(mem, GetStockObject(NULL_BRUSH));
    HGDIOBJ ohpen = SelectObject(mem, hpen);
    Rectangle(mem, myWaku.at(wk).left, myWaku.at(wk).top, myWaku.at(wk).right, myWaku.at(wk).bottom);
    SelectObject(mem, ohpen);
    DeleteObject(hpen);
        HGDIOBJ obmp = SelectObject(mem, bmp);
(途中省略)
        SelectObject(mem, obmp);
        DeleteObject(bmp);

リソースの開放が必要な場所は、以上の3か所かと存じます。
お忙しいところを恐縮ですが、御回答いただければ幸いです。

補足

止まった時点で、ステップイン(でいいのでしょうか?)をクリックしたところ、最初に次のような画面が表示されました。
イメージ説明

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Bull

    2019/12/22 11:20

    リソース不足を疑っているのでしたら、GDI オブジェクトはどうなっていますか?
    GDI オブジェクトと USER オブジェクトはタスクマネージャで確認出来ます。

    キャンセル

  • TAKASE_Hiroyuki

    2019/12/22 18:44

    御回答いただき、ありがとうございました。
    なにしろ、Visual Studio を使い始めてから日が浅いため、なかなか使いこなすことができません。この場合の、タスクマネージャーは、Visual Studio の機能ではなく、Windows10 のタスクマネージャーだと思いますが、間違いありませんでしょうか。一応「見る」ことはできるのですが、「どういう風に分析すればいいのか」が分かっていません。今後、さらに勉強しようと思います。

    キャンセル

回答 2

checkベストアンサー

+1

大きなリソースリーク部分が2箇所あります。

WM_PAINTメッセージのハンドリング部分で CreateCompatibleDCでデバイスコンテキストを作成していますが、DeleteDCで削除していません。

CreateCompatibleDC function - Microsoft Docs

更に、GetDCで取得したデバイスコンテキストも、ReleaseDCで解放されていません。

GetDC - Microsoft Docs

CreateCompatibleDCで作ったらDeleteDCで削除、GetDCで取得したらReleaseDCで解放するようにしてください。

尚、WM_PAINTメッセージの処理では、GetDCでデバイスコンテキストを取得するというよりは、WM_PAINTメッセージ受信時に必ず呼ばなければならないBeginPaintで返されるものを使うのが一般的です。

PAINTSTRUCT ps;
HDC hdc;

// WM_PAINTメッセージ受信部分でははBeginPaintのDCが使える。
hdc = BeginPaint(hWnd, &ps);
// hdc = GetDC(hWnd); これで取得できたものと同等に使える

...hdc を使って色々と処理。

// ReleaseDC(hWnd, hdc);
EndPaint(hWnd, &ps);

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/22 19:02

    御回答いただき、ありがとうございました。
    ご指摘いただいたことについて、WM_PAINT の中身を書き換えたところ、メモリ使用量はほとんど増えなくなりました。絶対に大丈夫かと言われると、私自身は心もとないのですが、以前に比べると明らかに改善されたと思います。ありがとうございました。

    キャンセル

  • 2019/12/22 19:15

    回答中には書かなかったのですが、こちらでは一応、ご提示のコードをWindows 7(64ビット)、Visual Studio 2019でGDIやUSERのハンドルがリークしていないことを確認済みです。コメントでBullさんがアドバイスされていますが、タスクマネージャーでGDIオブジェクトやUSERオブジェクトの数が稼働中に増え続けてないか確認することで、より確実な対応となると思います。

    キャンセル

+1

VisualStudio使ってるならデバッグ機能使いましょうよ。
その止まった状態で停止ボタン押したらどうなりますか?
そして、そこからワンステップづつ実行させたら、どこで止まってるのかわかると思います。

さて、どこでどうなって止まってる(とみえる)んでしょうか。


その止まった状態で停止ボタン押したらどうなりますか?(二回目

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/22 18:46

    御回答いただき、ありがとうございました。
    追記にも書きましたが、どうも、うまく表示されません。Visual Studio の使い方そのものを、さらに勉強しようと思います。
    なお、「一番最初からワンステップずつ実行させたらどうなるのか」は、やってみました。同じような場所を何度も何度も行ったり来たりしたあと、WM_PAINT等に進み、だんだん出力が変化していく様子がわかりました。そのことだけでも、とても勉強になりました。ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.76%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • C++に関する質問
  • おそらくリソース不足と思われますが、アプリが止まってしまいます。Visual Studio 2019 C++