Win32API ファイルアクセスの色々(メモ)

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);
  }
```