0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

車輪の再発明 : Windows専用(?) たぶん便利なThreadクラスを自作した

Last updated at Posted at 2025-02-28

1.はじめに

MFCでスレッドに関するクラスというと、以下があります。

「でも、MessagePump なんて大それたことしないんだけどな~。大げさだな~」って思いました。
次に候補に挙がるのは Win32 API の CreateThread です。

ここにはさらっととんでもないことか書いてあります。

C ランタイム ライブラリ (CRT) を呼び出す実行可能ファイル内のスレッドは、CreateThread と ExitThreadを するのではなく、スレッド管理に _beginthreadex 関数と _endthreadex 関数 使用する必要があります。これには、マルチスレッド バージョンの CRT を使用する必要があります。 CreateThread を使用して作成されたスレッドが CRT を呼び出すと、CRT はメモリ不足の状態でプロセスを終了する可能性があります。

原文でも以下ですから誤翻訳でもなさそうです。

A thread in an executable that calls the C run-time library (CRT) should use the _beginthreadex and _endthreadex functions for thread management rather than CreateThread and ExitThread; this requires the use of the multithreaded version of the CRT. If a thread created using CreateThread calls the CRT, the CRT may terminate the process in low-memory conditions.

一方、std::thread だと以下の点が物足りないと思います

  • スレッドが動作中なのかどうかがわからない
  • (使うかどうかはともかく)実行したスレッドの戻り値がわからない

考えるのが面倒になってきました。

2. と、いうことで __beginthreadex を元に スレッドクラスを作りました。

まずはヘッダ。

threadbase.h

// ThreadBase.h
#pragma once

#include	<windows.h>

class CThreadBase
{
public:
	CThreadBase();
	virtual ~CThreadBase();

	HANDLE	CreateThread(unsigned int (__stdcall* start_address)(void*),
		void* pParam = nullptr, void* pSecurity = NULL,
		unsigned int stack_size = 0, unsigned int initflag = 0, unsigned int* thrdaddr = NULL);

	void	join();
	BOOL	isThreadRunning();
	BOOL	GetExitCodeThread(LPDWORD  pExitCode);

private:
	static unsigned int __stdcall ThreadBaseRun(void* pParam);

	unsigned int (__stdcall* m_pFunc)(void*);
	void*						m_pParam;
	HANDLE						m_hThread;
};

引数の順番は好みの問題です。セキュリティ記述子などほとんど使ったことがないもの。
そんなのデフォルト引数 にして NULLを入れておけばいいのではないか。で、cpp。

ThreadBase.cpp

// ThreadBase.cpp
#include	"ThreadBase.h"

CThreadBase::CThreadBase()
	: m_pFunc(NULL)
	, m_pParam(NULL)
	, m_hThread(INVALID_HANDLE_VALUE)
{
	// no more!
}

CThreadBase::~CThreadBase()
{
	join();
}

void	CThreadBase::join()
{
	if (m_hThread != INVALID_HANDLE_VALUE) {
		WaitForSingleObject(m_hThread, INFINITE);
		CloseHandle(m_hThread);
		m_hThread = INVALID_HANDLE_VALUE;
	}
}

BOOL	CThreadBase::isThreadRunning()
{
	if (m_hThread == INVALID_HANDLE_VALUE) {
		return FALSE;
	}
	return (WaitForSingleObject(m_hThread, 0) == WAIT_TIMEOUT);
}


BOOL	CThreadBase::GetExitCodeThread(LPDWORD  pExitCode)
{
	if (m_hThread == INVALID_HANDLE_VALUE) {
		return FALSE;
	}
	return (::GetExitCodeThread(m_hThread, pExitCode));
}

HANDLE	CThreadBase::CreateThread(unsigned int (__stdcall* start_address)(void*),
	void* pParam, void* pSecurity, unsigned int stack_size, unsigned int initflag, unsigned int * thrdaddr)
{
	// スレッド起動中の再起動を防ぐ
	if ((nullptr == start_address) || (m_hThread != INVALID_HANDLE_VALUE))
	{
		return INVALID_HANDLE_VALUE;
	}

	HANDLE hRet = (HANDLE)(_beginthreadex(pSecurity, stack_size, ThreadBaseRun,
		this, initflag, thrdaddr));

	m_hThread = hRet;
	m_pFunc = start_address;
	m_pParam = pParam;

	return hRet;
}

unsigned int __stdcall CThreadBase::ThreadBaseRun(void* pParam)
{
	CThreadBase* pThis = static_cast<CThreadBase*>(pParam);
	
	unsigned int (__stdcall * pFunc)(void*) = pThis->m_pFunc;

	unsigned int uRet = pFunc(pThis->m_pParam);
	_endthreadex(uRet);

	// この文は実行されない
	return uRet;
}

3. あとはよろしく

CThreadBase を継承して使ってください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?