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

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

ただいまの
回答率

90.02%

WinAPI 子スレッドにおけるキューの取得・メッセージループ

解決済

回答 1

投稿

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

Weapon

score 79

前提・実現したいこと

WinAPI 子スレッドでの描画でキューを作りメッセージループとご教授いただいたのですが子スレッドにおいてキューはどうやって取得するのでしょうか?
メインウィンドウの場合GetMessageで取得してDispatchMessageからWindowProcにメッセージが渡っていたと思っていましたがこの場合のスレッドはウィンドウではないからWindowProcではないでしょうしスレッドの関数そのもののThreadProcとも違いますよね.
どのようにメッセージループを実装すればいいのでしょうか?

またプロシージャ内のswitch前,switch下(case前),グローバル宣言それぞれでstaticで変数宣言することにどのような違いがあるのでしょう?

発生している問題・エラーメッセージ

//main
#ifndef UNICODE
#define UNICODE
#endif UNICODE

#include <windows.h>
#include <process.h>
#include "render thread.h"
#include "Header.h"

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWindowProc(HWND, UINT, WPARAM, LPARAM);

HINSTANCE hInst;

RECT rc;
HWND chwnd;

static unsigned thid;

const wchar_t CLASS_NAME[] = L"CLASS";
const wchar_t CHILD_CLASS_NAME[] = L"CHILD_CLASS";

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR nCmdLine, int nCmdShow) {
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(nCmdLine);

    hInst = hInstance;

    WNDCLASSEX wc = {
        sizeof(WNDCLASSEX),CS_VREDRAW | CS_HREDRAW, WindowProc,
        0, 0, hInstance,
        NULL, (HCURSOR)LoadCursor(NULL, IDC_ARROW),(HBRUSH)GetStockObject(GRAY_BRUSH),
        NULL, CLASS_NAME, NULL
    };

    RegisterClassEx(&wc);

    HWND hwnd = CreateWindowEx(
        0, CLASS_NAME, L"Template", WS_OVERLAPPEDWINDOW|WS_CLIPCHILDREN,
        50, 50, 960, 525,
        NULL, NULL, hInstance, NULL
    );

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    MSG msg = {};

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_CREATE:
    {
        WNDCLASSEX wcx = {
        sizeof(WNDCLASSEX),CS_VREDRAW | CS_HREDRAW, ChildWindowProc,
        0, 0, hInst,
        NULL, (HCURSOR)LoadCursor(NULL, IDC_ARROW),(HBRUSH)GetStockObject(WHITE_BRUSH),
        NULL, CHILD_CLASS_NAME, NULL
        };

        RegisterClassEx(&wcx);

        chwnd = CreateWindowEx(
            0, CHILD_CLASS_NAME, L"", WS_CHILD|WS_VISIBLE|WS_THICKFRAME,
            0, 0, 0, 0,
            hwnd, NULL, hInst, NULL
        );

        ShowWindow(chwnd, SW_SHOW);
        UpdateWindow(chwnd);
        }
        break;

    case WM_SIZE:
    {
        GetClientRect(hwnd, &rc);
        MoveWindow(chwnd, 5, (rc.bottom - rc.top - (rc.right - rc.left) / 2)/2, (rc.right - rc.left) / 2 - 5, (rc.right - rc.left) / 2 - 5, TRUE);
        MoveWindow(form, (rc.right - rc.left) * 3 / 4 , 150, 100, 50, TRUE);
        MoveWindow(button, (rc.right - rc.left) * 3 / 4 + 110, 150, 100, 50, TRUE);
    }
    break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK ChildWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        static RENDTHDATA mrtd;
    case WM_CREATE:
    {
        GetClientRect(GetParent(hwnd), &rc);
        mrtd.hwnd = hwnd;
        mrtd.hdc = GetDC(hwnd);
        mrtd.size = (rc.right - rc.left) / 2 - 5;

        wchar_t mess[1024] = { 0 };
        wsprintf(mess, L"HWND : %d\n HDC : %x\nSize : %d", mrtd.hwnd, mrtd.hdc, mrtd.size);
        MessageBox(NULL, mess, L"Alert", MB_OK);

        _beginthreadex(NULL, 0, (_beginthreadex_proc_type)render, &mrtd, 0, &thid);
        PostThreadMessage(thid, WM_NULL, 0, 0);

    }
        break;

    case WM_PAINT:

        PostThreadMessage(thid, TM_PAINT, 0, 0);
        break;

    case WM_DESTROY:
        PostThreadMessage(thid, TM_PAINT, 0, 0);
        _endthreadex(0);
        PostQuitMessage(0);
        return 0;
    }

    DefWindowProc(hwnd, uMsg, wParam, lParam);
}
//Header.h
#pragma once

#include <windows.h>

typedef struct _tagRENDTHDATA {
    HWND hwnd;
    HDC hdc;
    int size;
}RENDTHDATA;

#define TM_CREATE (WM_APP+1)
#define TM_DESTROY (WM_APP+2)
#define TM_PAINT (WM_APP+3)
//render thread.h
#pragma once

#define UNICODE
#include <windows.h>

DWORD WINAPI render(LPVOID);
//render thread.cpp
#define UNICODE

#include <windows.h>
#include "render thread.h"
#include "Header.h"

int width =10000;
int height = 10000;

DWORD WINAPI render(LPVOID lp) {

    RENDTHDATA *rtd = (RENDTHDATA *)lp;

    HWND hwnd = rtd->hwnd;
    HDC hdc = rtd->hdc;
    int size = rtd->size;

    static MSG tMsg = {};

    while (GetMessage(&tMsg, hwnd, 0, 0)>0) {
        switch (tMsg.message) {
            static DWORD **image;
            static DWORD *image_c;
            static DWORD *dib;
            static BITMAPINFO bmpi;
        case TM_CREATE:
        {
            MessageBox(NULL, L"CALLED", L"Alert", MB_OK);

            image = (DWORD**)malloc(sizeof(DWORD*)*width);
            image_c = (DWORD*)malloc(sizeof(DWORD)*width*height);
            for (int i = 0; i < width; i++)image[i] = image_c + i * height;
            for (int i = 0; i < width; i++)for (int j = 0; j < height; j++)image[i][j] = 0x000000ff;

            dib = (DWORD*)calloc(width*height, sizeof(DWORD));

            ZeroMemory(&bmpi, sizeof(bmpi));
            bmpi.bmiHeader.biSize = sizeof(bmpi);
            bmpi.bmiHeader.biWidth = width;
            bmpi.bmiHeader.biHeight = height;
            bmpi.bmiHeader.biPlanes = 1;
            bmpi.bmiHeader.biBitCount = 32;
            bmpi.bmiHeader.biCompression = BI_RGB;
        }
        break;

        case TM_PAINT:
        {
            for (int i = 0; i < width; i++)for (int j = 0; j < height; j++)dib[i + j * width] = image[height - j - 1][i];    

            int a = StretchDIBits(
                hdc, 0, 0, width, height, 0, 0, size, size,
                dib, &bmpi,
                DIB_RGB_COLORS, SRCCOPY
            );
        }
        break;

        case TM_DESTROY:
        {
            free(dib);
            free(image_c);
            free(image);
        }
        break;

        }
    }
    return 0;
}


ちなみにMonte Carloの円周率導出を時間で徐々にプロットしていくというレンダリングのキャンバスを作ろうとしていました.
なおcase TM_PAINT1行目で行われているのは非効率ながらxy座標をDIBの線型配列に変換するものです.時間おきにプロット命令を入れるのでこの処理はcase TM_CREATEではしませんでした.プロット処理を追加したときはTM_TIMEなどを作成してそこを介してダブルバッファリングを考えています.

補足情報

Windows10 Pro
VisualStudio2017 Community

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • t_obara

    2018/12/19 19:02

    なぜスレッドで描画をしようとしているのか直前の記事で指摘されている通り疑問です。単にタイマーで徐々にプロットすれば良いのでは?スレッドをより深く学ぶためでしょうか?それなら題材としては筋が悪いので別な題材で学習することをお勧めします。

    キャンセル

  • Weapon

    2018/12/19 21:03

    激しく切り替わる大きなイメージはボトルネックになるのではと別スレッドを使い描画するものだと思っていましたがならないものなのでしょうか?基本的にはタイマーで徐々に描きたいのではなくシミュレーションのようなものの描画過程を見るのが私の書きたいコードなのでタイマーによる手法は取りませんでした.どこまで処理を分散できるのかを考えてやってはいますが同期のほうがむしろボトルネックになるのならばメインスレッドにも目を向けて書いていこうと思います.しかしどこまで別スレッドでできるのかどこまでやるべきなのかほぼ理解していないのでまたその都度お願いします.

    キャンセル

回答 1

checkベストアンサー

+1

メッセージを処理するスレッドとしては提示されているコードでほとんど問題ないレベルまでいってます。惜しいのが render thread.cpp の以下の部分

while (GetMessage(&tMsg, hwnd, 0, 0)>0)


で、これは特定のウィンドウからのメッセージを処理するわけではないので、以下のようになります。

while (GetMessage(&tMsg, NULL, 0, 0)>0)


この修正だけでスレッド間のメッセージ送受信は可能になります。
あとは DWM で処理されている現在では全く問題になりませんが、GetDC はなるべく HDC を使う直前で呼び出すのをお勧めします。DWM 導入以前はウィンドウを動かすとウィンドウのクライアント原点と GetDC した原点が異なるために描画位置がずれる問題がありました。まぁ、今では問題になることはありませんが・・・。

他にいろいろありますが、デバッグ中に気付くと思いますので割愛します。

またプロシージャ内のswitch前,switch下(case前),グローバル宣言それぞれでstaticで変数宣言することにどのような違いがあるのでしょう?

スコープが異なります。該当変数を使用できる範囲は以下のようになります。
・switch前のものは、直前の {} 内のみ
・switch下(case前)のものは switch の {} 内のみ
・グローバル宣言はそのファイル内で参照可能

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/12/20 09:00

    すこし話題からそれるようですが_endthreadexの引数 スレッド終了コードとは何ですか?

    キャンセル

  • 2018/12/20 09:08

    スレッドの呼び出し元がスレッドの終了コードを受けとるために渡す値ですね。呼び出し元は GetExitCodeThread で値を取得します。厳密にはスレッドハンドルがあればどのスレッドでも終了コードは取得できます。現在のコードではスレッドハンドルの方は扱っていないので取得する場合、スレッドハンドルも別途保管しておく必要がありますね。PostThreadMessageで使っているスレッドIDとは別物です。

    キャンセル

  • 2018/12/20 09:45

    ハンドルで終わらせたいスレッドを指定するのではなくハンドルから取得できる値を介してスレッドを終わらせるという認識でいいですか?

    キャンセル

  • 2018/12/20 09:59

    スレッドを終了させるのはスレッド自体のendthreadの呼び出しかスレッドの関数の終了です。GetExitCodeThreadはスレッドが終了した後で値を取得するのみです。終わらせる動作はありません。

    キャンセル

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

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

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