Windowsの場合、やり方がいくつもあってそれぞれに適した用途もあるので、覚書として列挙しておきます。(網羅しきれてないですが)
別にそんなこだわる必要もないんですけどね^^;
ファイル名を指定する、これだけのことでも、MAX_PATH以上のファイル名への対応とか色々語るべきものがあります。
char filename[]="test.txt";
char basepath[MAX_PATH]{}, fullpath[MAX_PATH]{};
GetModuleFileName(NULL, basepath, MAX_PATH); PathRemoveFileSpec(basepath);
sprintf(fullpath, "%s\\test.txt", basepath);
printf("filename(fullpath)=%s\n", fullpath);
ファイルサイズの取得と読み込みバッファの確保についても、好みもあるし色々あります。
// struct __stat64 buf; _stat64(filename, &buf); const size_t fsz=(size_t)buf.st_size;
WIN32_FILE_ATTRIBUTE_DATA fad; GetFileAttributesEx(filename, GetFileExInfoStandard, &fad);
const size_t fsz=(size_t)((((uint64_t)fad.nFileSizeHigh)<<32)|(uint64_t)fad.nFileSizeLow);
printf("filesize=%zu\n\n", fsz);
// char *b=malloc(fsz+1);
// std::unique_ptr<char[], void (*)(void *)> b((char *)malloc(fsz+1), free);
std::vector<char> b(fsz+1);
memset(&b[0], 0, fsz+1);
size_t rsz=0;
普通にファイル読み込みをパッと書く場合には、Cランタイムを使っちゃいますね、私の場合
// 1: crt FILE
{
FILE *fp =fopen(filename, "rb");
{
rsz=fread(&b[0], 1, fsz, fp);
}
fclose(fp);
}
しかし、WINDOWSハンドルを使ったほうが細かい設定ができるので、途中で変換しちゃったり
// 2: FILE -> fd -> HANDLE
{
FILE *fp =fopen(filename, "rb");
{
HANDLE h =(HANDLE)_get_osfhandle(fileno(fp));
ReadFile(h, &b[0], (DWORD)fsz, (DWORD *)&rsz, NULL);
}
fclose(fp);
}
C++ streamを使ったり(私はあまり使わんけども)
// 3: c++ stream
{
std::ifstream s(filename, std::ios::binary);
{
rsz=s.read(&b[0], fsz).gcount();
}
s.close();
}
利用するインターフェースに合わせるためにstreamに変換したり
// 4: FILE -> -> fd -> stream, FILE -> -> fd -> HANDLE -> stream
{
FILE *fp =fopen(filename, "rb");
{
namespace ios =boost::iostreams;
ios::stream_buffer<ios::file_descriptor_source> sb(fileno(fp), ios::never_close_handle);
// ios::stream_buffer<ios::file_descriptor_source> sb((HANDLE)_get_osfhandle(fileno(fp)), ios::never_close_handle);
std::istream s(&sb);
rsz=s.read(&b[0], fsz).gcount();
sb.close();
}
fclose(fp);
}
gzipとかbzip2とかのフィルター噛ませたい場合とか、バッファ側もstreamにしたり
// 5: stream -> stream
{
FILE *fp =fopen(filename, "rb");
{
HANDLE h =(HANDLE)_get_osfhandle(fileno(fp));
namespace ios =boost::iostreams;
ios::stream_buffer<ios::file_descriptor_source> sb(h, ios::never_close_handle);
std::istream source(&sb);
ios::array_sink sink(&b[0], fsz);
rsz=ios::copy(source, sink, fsz);
sb.close();
}
fclose(fp);
}
Win32APIでの基本、ReadFile(ブロッキング)
// 6: HANDLE, blocking read
{
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
{
ReadFile(h, &b[0], (DWORD)fsz, (DWORD *)&rsz, NULL);
}
CloseHandle(h);
}
シリアルデバイス上のファイルだと、より低レベルのNtCreateFile/NtReadFileを使いたい場合が出てきます
// 7: HANDLE, blocking read (NtReadFile)
{
char ntfilename[MAX_PATH];
sprintf(ntfilename, "\\??\\%s", fullpath);
ANSI_STRING ansi_filename;
UNICODE_STRING uni_filename;
RtlInitString(&ansi_filename, ntfilename);
RtlAnsiStringToUnicodeString(&uni_filename, &ansi_filename, TRUE);
OBJECT_ATTRIBUTES oa{ sizeof(OBJECT_ATTRIBUTES), NULL, &uni_filename, OBJ_CASE_INSENSITIVE };
HANDLE h;
IO_STATUS_BLOCK iosb;
NTSTATUS status;
status =NtOpenFile(&h, FILE_GENERIC_READ, &oa, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
// printf("open : NTSTATUS=%x, IOSB=(%x, %llx)\n", status, iosb.Status, iosb.Information);
{
typedef ULONG (__stdcall *pNtReadFile)(HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PVOID IoStatusBlock,
PVOID Buffer,
ULONG Length,
PLARGE_INTEGER ByteOffset,
PULONG Key);
pNtReadFile NtReadFile =(pNtReadFile)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtReadFile");
LARGE_INTEGER ofs{};
status =NtReadFile(h, NULL, NULL, NULL, (PVOID)&iosb, (PVOID)&b[0], (ULONG)fsz, (PLARGE_INTEGER)&ofs, NULL);
if(status==0 /*STATUS_SUCCESS*/) rsz=(size_t)iosb.Information;
}
CloseHandle(h);
RtlFreeUnicodeString(&uni_filename);
}
UIスレッドでブロッキング処理をしてしまうのはまずい、ということでスレッドを作ってそこで読み込ませてみたり
// 8: HANDLE, blocking read in thread (CreateThread)
{
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
{
using ctx =struct{ HANDLE h; char *b; size_t fsz; size_t rsz; };
ctx c{ h, &b[0], fsz, 0 };
HANDLE t =CreateThread(NULL, 0, [](void *pv)->DWORD{
ctx *c=(ctx *)pv;
ReadFile(c->h, c->b, (DWORD)c->fsz, (DWORD *)&(c->rsz), NULL);
return 0;
}, (void *)&c, 0, NULL);
WaitForSingleObject(t, INFINITE);
CloseHandle(t);
rsz=c.rsz;
}
CloseHandle(h);
}
スレッドプール使おうよって言われて直したり
// 9: HANDLE, blocking read in thread (QueueUserWorkItem)
{
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
{
using ctx =struct{ HANDLE h; char *b; size_t fsz; size_t rsz; HANDLE hevent; };
ctx c{ h, &b[0], fsz, 0, CreateEvent(NULL, TRUE, FALSE, NULL) };
QueueUserWorkItem([](void *pv)->DWORD{
ctx *c=(ctx *)pv;
ReadFile(c->h, c->b, (DWORD)c->fsz, (DWORD *)&(c->rsz), NULL);
SetEvent(c->hevent);
return 0;
}, (void *)&c, WT_EXECUTEDEFAULT);
WaitForSingleObject(c.hevent, INFINITE);
CloseHandle(c.hevent);
rsz=c.rsz;
}
CloseHandle(h);
}
いや、更新された方のスレッドプール関数で、って言われたり
// 10: HANDLE, blocking read in thread (CreateTreadpoolWork)
{
using ctx =struct{ HANDLE h; char *b; size_t fsz; size_t rsz; HANDLE hevent; };
using ctxqueue =concurrency::concurrent_queue<ctx*>;
ctxqueue cq;
PTP_WORK w =CreateThreadpoolWork([](PTP_CALLBACK_INSTANCE, void *pv, PTP_WORK)->void{
auto cq=reinterpret_cast<ctxqueue *>(pv);
ctx *c;
if(cq->try_pop(c)==true){
printf("ThreadpoolWork\n");
ReadFile(c->h, c->b, (DWORD)c->fsz, (DWORD *)&(c->rsz), NULL);
SetEvent(c->hevent);
}
}, &cq, NULL);
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
{
ctx c{ h, &b[0], fsz, 0, CreateEvent(NULL, TRUE, FALSE, NULL) };
cq.push(&c);
SubmitThreadpoolWork(w);
WaitForSingleObject(c.hevent, INFINITE);
CloseHandle(c.hevent);
rsz=c.rsz;
}
CloseHandle(h);
CloseThreadpoolWork(w);
}
そこはノンブロッキングで非同期にやるのがいいんじゃね
// 11: HANDLE, nonblocking read
{
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
{
OVERLAPPED o{};
BOOL bret=ReadFile(h, &b[0], (DWORD)fsz, NULL, &o);
if(bret==FALSE){
if(GetLastError()==ERROR_IO_PENDING){
GetOverlappedResult(h, &o, (DWORD *)&rsz, TRUE); // block
}
}
}
CloseHandle(h);
}
APCで読み込み完了まで待機させとこうよ
// 12: HANDLE, nonblocking read, callback APC
{
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
{
OVERLAPPED o{};
ReadFileEx(h, &b[0], (DWORD)fsz, &o, [](DWORD e, DWORD rsz, OVERLAPPED *o)->void{ printf("APC\n"); });
SleepEx(INFINITE, TRUE);
rsz=o.InternalHigh;
}
CloseHandle(h);
}
APCよりIOCPだよ
// 13: HANDLE, nonblocking read, callback IOCP
{
HANDLE hiocp=CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 1);
using ctx =struct{ HANDLE h; char *b; size_t fsz; size_t rsz; HANDLE hevent; HANDLE hiocp; };
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
{
ctx c{ h, &b[0], fsz, 0, CreateEvent(NULL, TRUE, FALSE, NULL), hiocp };
HANDLE t =CreateThread(NULL, 0, [](void *pv)->DWORD{
ctx *c=(ctx *)pv;
while(true){
DWORD sz;
ULONG_PTR k;
OVERLAPPED *o;
BOOL ret=GetQueuedCompletionStatus(c->hiocp, &sz, &k, &o, INFINITE);
if(o==((OVERLAPPED *)((DWORD_PTR)-1))) return 0;
if(ret==TRUE){ printf("IOCP\n"); c->rsz=sz; SetEvent(c->hevent); }
}
}, (void *)&c, 0, NULL);
CreateIoCompletionPort(h, hiocp, (ULONG_PTR)&h, 0);
OVERLAPPED o{};
ReadFile(h, &b[0], (DWORD)fsz, NULL, &o);
WaitForSingleObject(c.hevent, INFINITE);
PostQueuedCompletionStatus(hiocp, 0, 0, ((OVERLAPPED *)((DWORD_PTR)-1)));
WaitForSingleObject(t, INFINITE);
CloseHandle(t);
CloseHandle(c.hevent);
rsz=c.rsz;
}
CloseHandle(h);
CloseHandle(hiocp);
}
いやいやここでスレッドプールだよ
// 13: HANDLE, nonblocking read, callback ThreadpoolIo
{
using ctx =struct{ HANDLE h; char *b; size_t fsz; size_t rsz; HANDLE hevent; };
using ctxqueue =concurrency::concurrent_queue<ctx*>;
ctxqueue cq;
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
{
ctx c{ h, &b[0], fsz, 0, CreateEvent(NULL, TRUE, FALSE, NULL) };
cq.push(&c);
PTP_IO w=CreateThreadpoolIo(h, [](PTP_CALLBACK_INSTANCE, void *pv, void *o, ULONG result, ULONG_PTR rsz, PTP_IO)->void{
auto cq=reinterpret_cast<ctxqueue *>(pv);
ctx *c;
if(cq->try_pop(c)==true){
printf("ThreadpoolIo\n");
c->rsz=rsz;
SetEvent(c->hevent);
}
}, &cq, NULL);
StartThreadpoolIo(w);
OVERLAPPED o{};
ReadFile(h, &b[0], (DWORD)fsz, NULL, &o);
WaitForSingleObject(c.hevent, INFINITE);
WaitForThreadpoolIoCallbacks(w, TRUE);
CloseThreadpoolIo(w);
CloseHandle(c.hevent);
rsz=c.rsz;
}
CloseHandle(h);
}
追記:) ついでにcpprestの非同期streamを使った場合を書いてみた。こんな感じかね?
// 14: cpprest async-stream, nonblocking read, callback by task
{
concurrency::streams::rawptr_buffer<char> p(&b[0], b.size(), std::ios::out);
auto s=concurrency::streams::fstream::open_istream(utility::conversions::to_string_t(filename), std::ios::in|std::ios::binary).get();
{
if(s.is_open()==true){
s.read(p, fsz).then([&](size_t sz){ rsz=sz; }).then([=](){ s.close(); }).wait();
}
}
更に追記:) スレッドの代わりにコルーチン使う場合こんな書き方?
// 15: HANDLE, blocking read in coroutine
{
HANDLE h =CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
{
using ctx =struct{ HANDLE h; char *b; size_t fsz; size_t rsz; };
ctx c{ h, &b[0], fsz, 0 };
struct s
{
s() =delete;
static winrt::IAsyncOperation<size_t> read(ctx *pv){
ctx *c=(ctx *)pv;
co_await winrt::resume_background();
ReadFile(c->h, c->b, (DWORD)c->fsz, (DWORD *)&(c->rsz), NULL);
co_return c->rsz;
};
};
auto s_op{ s::read(&c) };
if(s_op.Status()!=winrt::AsyncStatus::Completed){
printf("not yet complete\n");
HANDLE ev =CreateEvent(NULL, TRUE, FALSE, NULL);
s_op.Completed([&](auto&&, auto&&){ SetEvent(ev); });
WaitForSingleObject(ev, INFINITE);
CloseHandle(ev);
}
rsz=s_op.get();
}
CloseHandle(h);
}