LoginSignup
0
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-08-04

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