Tips その8


異なるプロセスで同じメモリを共有するには

Win32になってから、プロセスが異なるとお互いのメモリを見ることができなくなっています。これは安全性のためなんでしょうね。よそのプログラムからメモリをいじられるというのはマズイだろうと。しかし、プログラムによっては、同じメモリ領域を異なるプロセスから参照したい場合もあるわけです。

プロセスが異なっても見えるデータというのは「ファイル」ですよね。しかしファイルは基本的にハードディスクなどの外部記憶装置に置かれているものです。これにいちいちアクセスしていたのでは処理速度が低下してしまいます。なんとかそのファイルをメモリ上につくれないか?つくれるんですな、実は。これを「メモリマップドファイル(Memory Mapped File)」といって、その名の通り、メモリ上にマップされたファイルなのです(以降、MMFと呼びます)。

では、その使い方。MMFをつくるには CreateFileMapping() Win32API関数を使用します。これで一旦できたMMFは、破棄されるまで何度でも開いたり閉じたりすることができます。また、既に他のプロセスによって同じMMFがつくられている場合は、Windows内部で参照数がカウントアップされて、同じMMFのハンドルが得られます。

MMFを開くには OpenFileMapping() Win32API関数を使用します。これでファイルハンドルが得られます。開いただけでは見えないので、これを見るためのビューを割り当てます。これは MapViewOfFile() で行います。見終わったら、まず UnmapViewOfFile() でビューを解放。で、CloseHandle() でファイルハンドルを閉じます。

使い終わったMMFは、プログラム終了時などに必ず破棄します。破棄は CloseHandle() でMMFのハンドルを渡せば良いです。その一連のコード例を書いてみます。

{ 共有するデータの型 }
type

  PMMFData = ^TMMFData;
  TMMFData = packed record // ここは任意に変更
    valwrd: WORD;
    valint: Integer;
    valdbl: Double;
  end;

{ MMFを作成してそのハンドルを返す }
function
CreateMMF(MMFName: String): THandle; // MMFName はMMFの名前
begin

  // MMF作成
  Result:= CreateFileMapping($FFFFFFFF,
                             nil,
                             PAGE_READWRITE,
                             0,
                             SizeOf(TMMFData),
                             PChar(MMFName));

end;

{ MMFのビューを開く }
procedure OpenMMF(var h: THandle; var P: Pointer; MMFName: String);
begin

  // MMFを開く
  h := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, MMFName);

  // MMFのビューを取得
  if h <> 0 then
    P := MapViewOfFile(h, FILE_MAP_ALL_ACCESS, 0, 0, 0);

end;

{ MMFのビューを閉じる }
procedure CloseMMF(h: THandle; P: Pointer);
begin

  // MMFのビューを解放
  UnmapViewOfFile(P);

  // MMFを閉じる
  CloseHandle(h);

end;


{ MMFを解放 }
procedure FreeMMF(h: THandle);
begin

  // MMFの解放
  CloseHandle(h);

end;

ここで TMMFData というのは何でもいいです。ただ、共有するプロセス同士で同じ型である必要があります。

では、上記の関数を使って実際にデータの読み書きをしてみます。

const
  MMF_NAME = 'MEMORY_MAPPED_FILE_NAME'; // MMFの名前 何でもいい


type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
    hMMF: THandle;
  public
    { Public 宣言 }
  end;

{ フォーム生成時 }
procedure
TForm1.Create(Sender: TObject);
begin

  // MMF作成
  hMMF:= CreateMMF(MMF_NAME);

end;

{ フォーム破棄時 }
procedure TForm1.FormDestroy(Sender: TObject);
begin

  // MMF解放
  FreeMMF(hMMF);

end;


{ ボタンクリックで TData の valint を表示してみる }
procedure TForm1.Button1Click(Sender: TObject);
var
  h   : THandle;
  p   : Pointer;
  buf : Integer;
begin

  // ビューを開く
  OpenMMF(h, p, MMF_NAME);

  if h <> 0 then
  begin

    // MMFに保持されている valint を取得
    buf:= PMMFData(p)^.valint;

    // ポップアップ表示
    ShowMessage(Format('valint = %d', [buf]));

    // ビューを閉じる
    CloseMMF(h, p);

  end;

end;

{ ボタンクリックで TData の valint を変更してみる }
procedure TForm1.Button2Click(Sender: TObject);
var
  h   : THandle;
  p   : Pointer;
begin

  // ビューを開く
  OpenMMF(h, p, MMF_NAME);

  if h <> 0 then
  begin

    // MMFに保持されている valint を 1 に変更
    PMMFData(p)^.valint := 1;

    // ビューを閉じる
    CloseMMF(h, p);

  end;

end;

ちなみに、Windows内部では CreateFileMapping() でつくられた、若しくは参照されたMMFの数を数えていて、CreateFileMapping() をするたびにカウントアップし、CloseHandle() するたびにカウントダウンしています。このカウント数が 0 になると自動的にMMFは消滅します。よくできてますな。


既に宣言されている変数のアドレスを直接参照したい

既に宣言されている変数を別の型として操作したい場合に便利な方法です。Delphi では absolute 宣言で絶対アドレスが指定できるので、これを利用します。

次の例は、Double型の変数をByte型配列として扱い、バイトオーダを逆順にする例です

procedure SwapDouble(var src: Double);
var
  ary : Array [0..7] of Byte absolute src; // srcと同じメモリ領域を占有
  tmp : Array [0..7] of Byte;
  i   : Integer;
begin

  // まずテンポラリに全てコピー
  ZeroMemory(@tmp, 8);
  CopyMemory(@tmp, @ary, 8);

  // 1byteずつ入れ替え
  for i:= 0 to 7 do
  begin
    ary[i]:= tmp[7-i]; // ary を変更することで src も変更される
  end;

end;

これはいろいろ応用ができそうです。


[ Delphi へ戻る ]