パイプでメモリをストリームにする

メモリ上にあるデータをあたかもファイルのように読みたいというのを以前に考えて、C++ のストリームクラスとして定義したことがある。

しかし、これはもちろん C++ でなければ使えない。 もう少し低水準の API を用いて実現できないかと考えたとき、 Windows API のパイプはほぼファイルと同じインターフェイスで使えることを思い出した。 シークが出来ないという制限はついてしまうが、それを除けば見掛け上はファイルと同じ形で処理できるのではないかと考え、実装してみた。

// memstream.h
HANDLE CreateMemoryFile(const LPVOID data, const DWORD data_size);
// memstream.c
#include <windows.h>
#include <process.h>
#include "memstream.h"

struct writer_args {
  HANDLE hWritePipe;
  LPVOID data;
  DWORD data_size;
};

static const int perround = 1024*64;

static void writer(void* args) {
  struct writer_args* wargs = args;
  char* data = wargs->data;
  DWORD data_size = wargs->data_size;
  HANDLE hWritePipe = wargs->hWritePipe;
  free(wargs);
  DWORD written_size;
  DWORD writing_size;
  
  for(DWORD rest=data_size; rest>0; data+=written_size, rest-=written_size) {
    writing_size = rest<perround ? rest : perround;
    if(!WriteFile(hWritePipe, data, writing_size, &written_size, NULL)) break;
  }

  CloseHandle(hWritePipe);
  _endthread();
}

HANDLE CreateMemoryFile(const LPVOID data, const DWORD data_size) {
  HANDLE rp, wp;
  BOOL result = CreatePipe(&rp, &wp, NULL, 0);
  if(!result) return INVALID_HANDLE_VALUE;
  struct writer_args* wargs = malloc(sizeof(struct writer_args));
  wargs->hWritePipe = wp;
  wargs->data = data;
  wargs->data_size = data_size;
  _beginthread(writer, 0, wargs);
  return rp;
}

これは以下のような要領で使うことが出来る。

#include <windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include "memstream.h"

int main(void) {
  char* sample_str="Hello, this is file mapped memory sample";
  HANDLE mfh = CreateMemoryFile(sample_str, strlen(sample_str));
  int fd = _open_osfhandle((int)mfh, _O_RDONLY);
  FILE *fp = _fdopen(fd, "r");
  int ch;
  while((ch=getc(fp)) != EOF) putchar(ch);
  fclose(fp);
  return 0;
}

ウェブ上を検索してみるとスレッドを使わずに一気にパイプに書き込んでしまう方法を紹介しているものもある。 しかし、データ量が充分に小さいときを除いてはパイプのバッファサイズを越えてしまう可能性がある。 そのとき、少しづつでもパイプから読み出してデータを消費するのならばいずれは書込みも完了するのだが、シングルスレッドでは永遠に待つようになってしまう。 CreatePipe API の第四引数でパイプのバッファサイズを指定しても、あくまでヒントとして活用されるに過ぎず、その大きさのバッファを確保することを保証していないということに注意して欲しい。 パイプのバッファサイズ (としてヒントを与えた数値) より少ないデータ量しか書込まないからロックしないとは言い切れず、シングルスレッドで確実にロックしないようにする方法は (MSDN における CreatePipe に関する記述の範囲内では) 読み取れなかった。 そんなわけで、パイプを書き込む側はスレッドにするのが安全であると判断して上記のように実装した次第である。

Document ID: ecd99cc5c21bd06439374c829b044f4b