[WindowsAPI]古典的非同期I/Oのサンプルコード

CPUバウンド、かつI/Oバウンドな処理(ファイルからのハッシュ計算など)の場合、非同期I/Oを使用することで処理時間が短縮する場合がある。

一般的に、ワーカースレッドとキューを用いた実装をする場合が多いかと思うが、ここでは1スレッド、そして古典的なWindows APIである `ReadFile`(の滅多に使用しない`lpOverlapped`パラメータ) を用いて非同期I/Oを実装したサンプルコードを掲載する。

## 処理イメージ
大体こんなフローです。
![処理フローチャート](https://qiita-image-store.s3.amazonaws.com/0/124276/bdb939ea-610f-f958-2536-4d81fc5036bc.png)


## 注意
- このサンプルコードが、本当に早い保証はありません。計測してください。
- 闇雲に非同期I/Oを使用すべきではない。
同期I/Oでも、OS等のキャッシュにより非同期I/Oとほぼ変わらない処理時間の場合が多い。
低速なCPUと低速なI/Oが想定される場合のみ非同期I/Oを検討するべきである。
- ハッシュ計算は古いAPIであるCryptoAPIを使用している。MicrosoftはCNG APIの使用を推奨している。


## サンプルコード
CPUバウンドの処理の例として、SHA-512を行っている。
非同期I/OはWindows 2000 などでも可能だが、SHA-512はWindows XP SP3以降でないと対応していない。

```c:main.c

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0502
#endif

#include <windows.h>
#include <tchar.h>
#include <wincrypt.h>
#include <bcrypt.h>

#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "crypt32.lib")

#define BUFSIZE (4 * 1024 * 1024)
#define SHA512LEN (512/8)

struct BUF
{
	LPBYTE lpBuffer;
	OVERLAPPED ol;
	DWORD dwBufferSize;
	DWORD dwReaded;
	BOOL fPending;
};

static void initBuf(struct BUF * pBuf)
{
	memset(pBuf, 0x00, sizeof(struct BUF));
	pBuf->lpBuffer = malloc(BUFSIZE);
	pBuf->dwBufferSize = BUFSIZE;
	//pBuf->ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
}

DWORD CalcHashAsync(LPCTSTR lpFileName, HCRYPTHASH hHash)
{
	struct BUF buffers[2] = {0};
	HANDLE hFile;
	BOOL fContinue = TRUE;
	BOOL index = 0;
	ULARGE_INTEGER offset = {0};
	DWORD dwError = ERROR_SUCCESS;

	hFile = CreateFile(lpFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED, NULL);
	if(INVALID_HANDLE_VALUE == hFile)
	{
		dwError = GetLastError();
		DebugBreak();
		return dwError;
	}

	initBuf(&buffers[0]);
	initBuf(&buffers[1]);

	while(fContinue)
	{
		// -------------------------------

		if(buffers[index].fPending)
		{
			DWORD dwPendingTick = timeGetTime();

			//_tprintf(TEXT("[%d]Wait Pending I/O...\n"), index);
			if(!GetOverlappedResult(hFile, &buffers[index].ol, &buffers[index].dwReaded, TRUE))
			{
				DWORD overlappedResult = GetLastError();
				fContinue = FALSE;

				if(overlappedResult != ERROR_HANDLE_EOF) 
				{
					dwError = overlappedResult;
					DebugBreak();
					break;
				}
			}
			_tprintf(TEXT("[%d] %u\n"), index, timeGetTime() - dwPendingTick);
		}

		// -------------------------------
		if(fContinue)
		{
			BOOL nextIndex = !index;

			buffers[nextIndex].dwReaded = 0;
			buffers[nextIndex].ol.Offset = offset.LowPart;
			buffers[nextIndex].ol.OffsetHigh = offset.HighPart;

			memset(buffers[nextIndex].lpBuffer, 0xCC, buffers[nextIndex].dwBufferSize);
			buffers[nextIndex].fPending = FALSE;
			_tprintf(TEXT("[%d]Start ReadFile(offset:0x%X%08X)...\n"), nextIndex, offset.HighPart, offset.LowPart );
			if(!ReadFile(hFile, buffers[nextIndex].lpBuffer, buffers[nextIndex].dwBufferSize, &buffers[nextIndex].dwReaded, &buffers[nextIndex].ol))
			{
				DWORD dwReadFileResult = GetLastError();
				switch(dwReadFileResult)
				{
				case ERROR_IO_PENDING:
					buffers[nextIndex].fPending = TRUE;
					break;
				case ERROR_HANDLE_EOF:
					fContinue = FALSE;
					break;
				default:
					fContinue = FALSE;
					dwError = dwReadFileResult;
					break;
				}
			}
			else
			{
				_tprintf(TEXT("[%d]ReadFile completed synchronously.\n"), nextIndex);
			}
			offset.QuadPart += buffers[nextIndex].dwBufferSize;
		}

		// -------------------------------
		if(buffers[index].dwReaded)
		{
			_tprintf(TEXT("[%d]CryptHashData(0x%08X)...\n"), index, buffers[index].dwReaded);
			if(!CryptHashData(hHash,  buffers[index].lpBuffer, buffers[index].dwReaded, 0))
			{
				dwError = GetLastError();
				DebugBreak();
				break;
			}
		}

		index = !index; // 0, 1, 0, 1 ...
	}

	CloseHandle(hFile);
	free(buffers[0].lpBuffer);
	free(buffers[1].lpBuffer);
	return dwError;
}
int _tmain(int argc, _TCHAR* argv[])
{
	HCRYPTPROV hProv = 0;
	HCRYPTHASH hHash = 0;
	BYTE hashVal[SHA512LEN] = {0};
	DWORD dwHashSize = sizeof(hashVal);
	TCHAR hashStr[SHA512LEN * 4 + 1] = TEXT("");
	DWORD cchStr = _countof(hashStr);

	if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
	{
		DebugBreak();
		return 1;
	}

	if(!CryptCreateHash(hProv, CALG_SHA_512, 0, 0, &hHash))
	{
		DebugBreak();
		CryptReleaseContext(hProv, 0);
		return 1;
	}

	CalcHashAsync(argv[1], hHash);


	if(CryptGetHashParam(hHash, HP_HASHVAL, hashVal, &dwHashSize, 0))
	{
		CryptBinaryToString(hashVal, sizeof(hashVal),CRYPT_STRING_HEX | CRYPT_STRING_NOCR,hashStr, &cchStr);
		_tprintf(TEXT("%s\n"), hashStr);
	}
	else
	{
		_ftprintf(stderr, TEXT("CryptGetHashParam failed.(0x%08X)\n"), GetLastError());
	}

	CryptDestroyHash(hHash);
	CryptReleaseContext(hProv, 0);
	return 0;
}
```