メッセージによるプロセス間通信


redball.gif目次へ戻る

API の稿の 第17章 メモリ操作 で説明したように、Win32 におけるメモリの仮想アドレス空間はプロセスごとに独立している。したがって、何かの情報をメモリに確保してそれを他のプロセスに渡したいとき、そのアドレスを教えてやっても、受け取る方のプロセスにとってそのアドレスは無意味なものになっている。通常はアクセス違反を起こしてどちらかのアプリが強制終了する。Win32 におけるプロセス間通信には、DDE、OLE、パイプ、クリップボード、メールスロットなどがあるらしいが、クリップボード以外では、わたしは Delphi のDDEについて、Delphi2 のときに試したことがあるだけで、ほかの方法についてはやったことがない。ここでは、メッセージを使って任意のメモリブロックをプロセス間でやりとりする方法を調べよう。

メッセージは、メッセージIDそのものがシステムグローバルであり、また、通信するプロセスのウィンドウハンドルもシステムレベルでユニークさが保証されている。したがって、プロセス間でメッセージをウィンドウをつかってやりとりすること自体には、何の問題もない。しかし、やりとりできる情報は、ID 以外には wParamlParam、それから SendMessage() の戻り値の result の三つの32ビットの数値に限られる。したがって、普通にメッセージをやりとりするだけでは、任意のデータを受け渡しすることはできない。ここの メッセージ受信専用ウィンドウ のところでやったように、文字列については、WM_GETTEXTWM_SETTEXT メッセージを使って受け渡しができる。これらは、ウィンドウテキストがウィンドウの属性の一つなので、ウィンドウズが仮想メモリ空間のアドレスの問題を自動的に解決してくれている例外的なメッセージなのである。しかし、これらのメッセージを使っても、文字列以外のデータを受け渡すことはできない。

OSが仮想メモリ空間のアドレスの問題を自動的に解決してくれている例外的なメッセージは、もう一つある。WM_COPYDATA というメッセージで、任意のメモリブロックをその先頭を指すポインタとサイズを指定して、異なるプロセスのウィンドウに送ると、受け取り先で問題なくメモリの内容のコピーができるのである。ここでは、この WM_COPYDATA メッセージを使って、任意のメモリブロックをプロセス間でやりとりすることを試してみよう。任意のメモリブロックには、文字列はもちろん、ビットマップやJPEGなどの画像や .wav などの音楽ファイルや、任意のレコード型の配列や可変長レコードなども読み書きできるので、ほとんどどんな形式のデータでもやりとりできるだろう。ここでは、TMemoryStream をデータのやりとりに使うバッファメモリとして利用する。そうすると、その Write()Read() メソッドで読み書きができるし、可変長レコードの場合は TReaderTWriter クラスを利用できる。また、SaveToStream()LoadFromStream() メソッドを持つクラスにはそのまま使えるだろう。

WM_COPYDATADelphi 付属の Win32.hlp によると

WM_COPYDATA  
wParam = (WPARAM) (HWND) hwnd;            // handle of sending window 
lParam = (LPARAM) (PCOPYDATASTRUCT) pcds; // pointer to structure with data 

となっており、wParam には送り側のウィンドウハンドルを設定する。lParam には、以下のレコード型を指すポインタを設定する。

type
  PCopyDataStruct = ^TCopyDataStruct;
  tagCOPYDATASTRUCT = packed record
    dwData: DWORD;    // 受け取り先に渡す任意の32ビット数値データ
    cbData: DWORD;    // lpData ポインタが指すメモリブロックのサイズ
    lpData: Pointer;   //  受け渡すメモリブロックの先頭を指すポインタ
  end;
  TCopyDataStruct = tagCOPYDATASTRUCT;
  COPYDATASTRUCT = tagCOPYDATASTRUCT;

したがって、MS をメモリバッファである TMemoryStream のインスタンスであるとすると

・dwData には、データの種類を表す(アプリ間で勝手に取り決めた)数値
・cbData には、MS.Size 
・lpData には、MS.Memory

を設定することにしよう。それでは具体的にやってみよう。最初は、WM_COPYDATA を調べるために一つのプロセス内で jpeg 画像を受け渡ししてみよう。Form1Button1 をおいて以下のようにする。

List 1

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    procedure WMCopyData(var Msg:TWMCopyData);message WM_COPYDATA;
    procedure JpegReceived(MS:TMemoryStream);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses
  Jpeg;

procedure TForm1.Button1Click(Sender: TObject);
var
  CDS:TCopyDataStruct;
  MS:TMemoryStream;
begin
  MS := TMemoryStream.Create;
  try
    MS.LoadFromFile('d:\orangecake.jpg');//適当な jpeg ファイルを読み込む
    CDS.dwData := 1234;
    CDS.cbData := MS.Size;
    CDS.lpData := MS.Memory;
    SendMessage(Handle,WM_COPYDATA,Button1.Handle,LPARAM(@CDS));
  finally
    MS.Free;
  end;
end;

procedure TForm1.JpegReceived(MS: TMemoryStream);
var
  JPG:TJpegImage;
begin
  JPG := TJpegImage.Create;
  try
    JPG.LoadFromStream(MS);
    Refresh;
    Canvas.Draw(10,10,JPG);
  finally
    JPG.Free;
  end;
end;

procedure TForm1.WMCopyData(var Msg: TWMCopyData);
var
  MS:TMemoryStream;
begin
   MS := TMemoryStream.Create;
   try
     MS.Write(Msg.CopyDataStruct^.lpData^,Msg.CopyDataStruct^.cbData);
     MS.Position := 0;
     case Msg.CopyDataStruct^.dwData of
       1234:JpegReceived(MS);
     end;
   finally
     MS.Free;
   end;
end;

end.
n01201.jpg

実行すると右図のようになる。

ここでは、Button1Click() でメモリストリームにJPEG画像をファイルから読み込み、それを SendMessage()WM_COPYDATA を使って Form1 に送っている。メモリアロケーションが送り先で処理されている間でも確保されている必要があるので、PostMessage() を使った非同期通信はできない。必ず SendMessage() を使うこと。Form1 では WM_COPYDATA をメッセージ処理ハンドラで TWMCopyData の形で捕まえている。

  TWMCopyData = packed record
    Msg: Cardinal;
    From: HWND;
    CopyDataStruct: PCopyDataStruct;
    Result: Longint;
  end;

そして、この CopyDataStruct から情報を読みとって、メモリストリームにコピーしている。それを、JpegReceived() に渡して画像を表示している。これで、WM_COPYDATA メッセージの使い方が分かったので、同じことを本当にプロセス間通信で行ってみよう。まず、Receiver の方を以下に示す。

List 2

unit ReceiverMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TReceiverForm = class(TForm)
  private
    { Private 宣言 }
  public
    procedure WMCopyData(var Msg:TWMCopyData);message WM_COPYDATA;
    procedure JpegReceived(MS:TMemoryStream);
  end;

var
  ReceiverForm: TReceiverForm;

implementation

{$R *.DFM}

uses
  Jpeg;

procedure TReceiverForm.JpegReceived(MS: TMemoryStream);
var
  JPG:TJpegImage;
begin
  JPG := TJpegImage.Create;
  try
    JPG.LoadFromStream(MS);
    Refresh;
    Canvas.Draw(10,10,JPG);
  finally
    JPG.Free;
  end;
end;

procedure TReceiverForm.WMCopyData(var Msg: TWMCopyData);
var
  MS:TMemoryStream;
begin
   MS := TMemoryStream.Create;
   try
     MS.Write(Msg.CopyDataStruct^.lpData^,Msg.CopyDataStruct^.cbData);
     MS.Position := 0;
     case Msg.CopyDataStruct^.dwData of
       1234:JpegReceived(MS);
     end;
   finally
     MS.Free;
   end;
end;

end.

これは、前のプログラムから Button1 とそのハンドラを削除しているだけである。Sender が送信先のウィンドウを見つけやすいように、Form1Name をオブジェクトインスペクタで ReceiverForm に変更している。そうすると、

var
  ReceiverForm: TReceiverForm;

のようになって、VCLのクラス名も自動的に変更される。VCLでは、言語上のクラス名とそのインスタンスが作るウィンドウのクラス名が一致しているので、OSを通しても TReceiverForm のクラス名で送信先のウィンドウを見つけられる。これをプロジェクトファイルを Receiver.dpr として保存し、コンパイルしておく。次に Sender を以下に示す。

List 3

unit SenderMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
  CDS:TCopyDataStruct;
  MS:TMemoryStream;
  hTarget:HWND;
begin
  MS := TMemoryStream.Create;
  try
    hTarget := FindWindow('TReceiverForm',nil);
    if hTarget <> 0 then begin
      MS.LoadFromFile('d:\orangecake.jpg');//適当な jpeg ファイルを読み込む
      CDS.dwData := 1234;
      CDS.cbData := MS.Size;
      CDS.lpData := MS.Memory;
      SendMessage(hTarget,WM_COPYDATA,Handle,LPARAM(@CDS));
    end;
  finally
    MS.Free;
  end;
end;

end.
n01202.jpg

これをプロジェクトファイルを Sender.dpr として保存する。このように 'TReceiverForm' のクラス名を使って送信相手のウィンドウを FindWindow() で見つけだしている。実行結果は右図のようになり、プロセス間でもJPEG画像の受け渡しがうまくいっていることを示している。

同じことを文字列の受け渡しの場合で試してみよう。Sender を以下に示す。Form1Memo1Button1 を置いて

List 4

unit SenderMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses
  Jpeg;

procedure TForm1.Button1Click(Sender: TObject);
var
  CDS:TCopyDataStruct;
  MS:TMemoryStream;
  hTarget:HWND;
begin
  MS := TMemoryStream.Create;
  try
    hTarget := FindWindow('TReceiverForm',nil);
    if hTarget <> 0 then begin
      MS.Write(PChar(Memo1.Lines.Text)^,Length(Memo1.Lines.Text)+1);
      CDS.dwData := 5678;
      CDS.cbData := MS.Size;
      CDS.lpData := MS.Memory;
      SendMessage(hTarget,WM_COPYDATA,Handle,LPARAM(@CDS));
    end;
  finally
    MS.Free;
  end;
end;

end.

ここでは、Memo1.Lines.TextPChar にキャストして、その逆参照を Length(Memo1.Lines.Text)+1 のサイズでメモリストリームに書き込んで送信している。末尾のヌル文字もで書き込んでおくと文字列の長さを VCLStrLen() 関数で求めることができる。一般に、書き込んだ内容のサイズとメモリストリームの Size プロパティーの内容は一致していないからである。Receiver を以下に示す。

List 5

unit ReceiverMain;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TReceiverForm = class(TForm)
    Memo1: TMemo;
  private
    { Private 宣言 }
  public
    procedure WMCopyData(var Msg:TWMCopyData);message WM_COPYDATA;
    procedure StringReceived(MS:TMemoryStream);
  end;

var
  ReceiverForm: TReceiverForm;

implementation

{$R *.DFM}

procedure TReceiverForm.StringReceived(MS: TMemoryStream);
var
  s:string;
begin
  SetString(s,PChar(MS.Memory),StrLen(PChar(MS.Memory)));
  Memo1.Lines.Text := s;
end;

procedure TReceiverForm.WMCopyData(var Msg: TWMCopyData);
var
  MS:TMemoryStream;
begin
   MS := TMemoryStream.Create;
   try
     MS.Write(Msg.CopyDataStruct^.lpData^,Msg.CopyDataStruct^.cbData);
     MS.Position := 0;
     case Msg.CopyDataStruct^.dwData of
       5678:StringReceived(MS);
     end;
   finally
     MS.Free;
   end;
end;

end.
n01203.jpg

このように、SetString() を使って、メモリストリームから文字列に写し取り、それを表示している。実行結果を右図にしめした。

以上のように、WM_COPYDATA メッセージを使って任意のメモリブロックをプロセス間で受け渡しするのは非常に簡単にできることが分かった。受け渡しするデータ形式ごとに異なる部分は、送信元でメモリストリームに書き込む部分と、受信先でメモリストリームから読み込む部分だけである。

実はメッセージをやりとりすること自体は簡単なのであるが、一番難しいのは、送信元で受信側のウィンドウハンドルをどうやって見つけるのか、という問題なのである。これまでは、Form1 を見つけやすいように Name プロパティーを変更してそのクラス名を FindWindow() を使って見つけてきた。これでは汎用性がないし、1対多の通信の場合では役に立たない。この問題は、ここの メッセージ受信専用ウィンドウ のところで作ったユニークなクラス名を指定してメッセージ受信専用ウィンドウを作る関数

function AllocMsgWnd(Method: TWndMethod; ClassName:string): HWND;
var
  DummyClass: TWndClass;
begin
  FillChar(DummyClass,SizeOf(TWndClass),#0);
  DummyClass.hInstance := hInstance;
  DummyClass.lpfnWndProc := @DefWindowProc;
  DummyClass.lpszClassName := PChar(ClassName);
  Windows.RegisterClass(DummyClass);

  Result := CreateWindowEx(WS_EX_TOOLWINDOW, DummyClass.lpszClassName,
    '', WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
  if Assigned(Method) then
    SetWindowLong(Result,GWL_WNDPROC,LongInt(MakeObjectInstance(Method)));
end;
n01204.jpg

を使って解決できる。これは、幅と高さがゼロのトップレベルウィンドウをつくるので EnumWindows()GetWindow() API で複数の受信ウィンドウを見つけられる。ここでは API with VCL第6章 ウィンドウの階層について で作った WinSearch.pas の中の EnumWindowsOfClassNameAsZOrder() 関数を少し改造して使ってみよう。それでは具体的にやってみよう。右図のように、ScrollBox1 とその中に Image1 を置き、その外に Button1OpenPictureDialog1 を置いて以下のようにする。

List 6

unit Main;

interface

uses
  Windows, Messages, SysUtils, Graphics, Controls, Forms, Dialogs,
  ExtDlgs, StdCtrls, ExtCtrls, Classes; // Classes を最後に持ってくること! 

type
  TForm1 = class(TForm)
    ScrollBox1: TScrollBox;
    Image1: TImage;
    Button1: TButton;
    OpenPictureDialog1: TOpenPictureDialog;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    AllocHandle:HWND;
    procedure WndMethod(var Msg:TMessage);
    procedure BitmapReceived(MS:TMemoryStream);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

type
  PHWndArray = ^THWndArray;
  THWndArray = array[0..50] of HWND;

const
  UniqueName = 'HogeHogeReceiver';

function AllocMsgWnd(Method: TWndMethod; ClassName:string): HWND;
var
  DummyClass: TWndClass;
begin
  FillChar(DummyClass,SizeOf(TWndClass),#0);
  DummyClass.hInstance := hInstance;
  DummyClass.lpfnWndProc := @DefWindowProc;
  DummyClass.lpszClassName := PChar(ClassName);
  Windows.RegisterClass(DummyClass);

  Result := CreateWindowEx(WS_EX_TOOLWINDOW, DummyClass.lpszClassName,
    '', WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
  if Assigned(Method) then
    SetWindowLong(Result,GWL_WNDPROC,LongInt(MakeObjectInstance(Method)));
end;

function GetClassNameStr(hWindow:HWND):string;
var
  p:PChar;
  ret:integer;
begin
  GetMem(p,100);
  ret := GetClassName(hWindow,p,100);
  SetString(result,p,ret);
  FreeMem(p);
end;

function EnumReceiver(var WA:THWndArray;ClassName:string):integer;
var
  hWindow:HWND;
begin
  WA[0] := 0;
  hWindow := GetWindow(Application.Handle,GW_HWNDFIRST);
  while (hWindow <> 0) do begin
    if (UpperCase(GetClassNameStr(hWindow)) = UpperCase(ClassName)) then
    begin
      WA[0] := WA[0]+1;
      WA[WA[0]] := hWindow
    end;
    hWindow := GetWindow(hWindow,GW_HWNDNEXT);
  end;
  result := WA[0];
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Image1.Left := 0;
  Image1.Top := 0;
  Image1.AutoSize := true;

  if AllocHandle = 0 then
    AllocHandle := AllocMsgWnd(WndMethod,UniqueName);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  DeallocateHwnd(AllocHandle);
  AllocHandle := 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MS:TMemoryStream;
  CDS:TCopyDataStruct;
  WA:THWndArray;
  i,numReceiver:integer;
begin
  OpenPictureDialog1.Filter := 'Bitmap files (*.bmp)|*.bmp';
  if OpenPictureDialog1.Execute then begin
    MS := TMemoryStream.Create;
    try
      MS.LoadFromFile(OpenPictureDialog1.FileName);
      CDS.dwData := 222;
      CDS.cbData := MS.Size;
      CDS.lpData := MS.Memory;
      numReceiver := EnumReceiver(WA,UniqueName);
      if numReceiver> 0 then begin
        for i := 1 to numReceiver do
          SendMessage(WA[i],WM_COPYDATA,Handle,LPARAM(@CDS));
      end; 
    finally
      MS.Free;
    end;
  end;
end;

procedure TForm1.BitmapReceived(MS: TMemoryStream);
begin
  Image1.Picture.Bitmap.LoadFromStream(MS);
end;

procedure TForm1.WndMethod(var Msg: TMessage);
var
  CDMsg:TWMCopyData;
  MS:TMemoryStream;
begin
  if Msg.Msg = WM_COPYDATA then begin
    CDMsg := TWMCopyData(Msg);
    MS := TMemoryStream.Create;
    try
      MS.Write(CDMsg.CopyDataStruct^.lpData^,CDMsg.CopyDataStruct^.cbData);
      MS.Position := 0;
      case CDMsg.CopyDataStruct^.dwData of
        222:BitmapReceived(MS);
      end;
    finally
      MS.Free;
    end;
  end else
    with Msg do
      Result := CallWindowProc(@DefWindowProc,AllocHandle,Msg,wParam,lParam);
end;

end.
n01205.jpg

コンパイルしてできた実行ファイルをエクスプローラからダブルクリックして、複数のインスタンスを立ち上げる。右図のように4つ起動して、そのどれかの Button1 を押してビットマップファイルを選択すると、すべてのインスタンスの Image1 にそれが表示される。

メッセージ受信専用ウィンドウは、FormCreate() でつくられ、FormDestroy() で破棄される。Button1 を押すと OpenPictureDialog1 からビットマップファイル名を取得して、それをメモリストリームに読み込み、EnumReceiver() で自分自身をも含むメッセージ受信ウィンドウの複数のハンドルを取得して順番に WM_COPYDATA でメモリストリームの内容を送信している。メッセージ受信専用ウィンドウのインスタンス化されたウィンドウ関数 TForm1.WndMethod() でこれをメモリストリームで受け取って BitmapReceived() に渡して Image1 に表示している。

最後にこの機能をコンポーネントにしよう。以下にその全リストをしめす。

List 7

unit SendDataUnit;

interface

uses
  Windows, Messages, SysUtils, Graphics, Controls, Forms, Classes;

type
PReceiverWndArray = ^TReceiverWndArray;
TReceiverWndArray = array[0..50] of HWND;

TSendData = class(TComponent)
private
  FUniqueName:string;
  FAllocWnd:HWND;
  FInitWnd:HWND;
  FSendMemory:TMemoryStream;
  FReceiveMemory:TMemoryStream;
  FDataKind:DWORD;
  FOnMessage:TNotifyEvent;
  function GetStr: string;
  procedure SetStr(const Value: string);
protected
  procedure WndMethod(var Message: TMessage);
  procedure InitProc(var Message: TMessage);
  function AllocMsgWnd: HWND;
  procedure MsgReceived(var CDMsg:TWMCopyData);virtual;
public
  constructor Create(AOwner: TComponent);override;
  destructor Destroy; override;
  procedure SendData;
  property SendMemory:TMemoryStream read FSendMemory;
  property ReceiveMemory:TMemoryStream read FreceiveMemory;
  property Str:string read GetStr write SetStr;
published
  property UniqueName:string read FUniqueName write FUniqueName;
  property DataKind:DWORD read FDataKind write FDataKind;
  property OnMessage:TNotifyEvent read FOnMessage write FOnMessage;
end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples',[TSendData]);
end;

{ TSendData }

constructor TSendData.Create(AOwner: TComponent);
begin
  inherited;
  FUniqueName := 'HogeHogeReceiver';
  if csDesigning in ComponentState then exit;
  FInitWnd := AllocateHWnd(InitProc);
  PostMessage(FInitWnd,WM_USER+200,0,0);
  FSendMemory := TMemoryStream.Create;
  FReceiveMemory := TMemoryStream.Create;
end;

destructor TSendData.Destroy;
begin
  if not (csDesigning in ComponentState) then begin
    DeallocateHWnd(FAllocWnd);
    FSendMemory.Free;
    FReceiveMemory.Free;
  end;
  inherited;
end;

procedure TSendData.InitProc(var Message: TMessage);
begin
  if Message.Msg <> WM_USER+200 then exit;
  DeallocateHWnd(FInitWnd);
  FAllocWnd := AllocMsgWnd;
end;

procedure TSendData.SetStr(const Value: string);
begin
  FSendMemory.Clear;
  FSendMemory.Write(PChar(Value)^,Length(Value)+1);
  FSendMemory.Position := 0;
end;

function TSendData.GetStr: string;
begin
  SetString(result,PChar(FReceiveMemory.Memory),
                     StrLen(PChar(FReceiveMemory.Memory)));
end;

procedure TSendData.WndMethod(var Message: TMessage);
var
  CDMsg:TWMCopyData;
begin
  if Message.Msg = WM_COPYDATA then begin
    CDMsg := TWMCopyData(Message);
    FReceiveMemory.Clear;
    FReceiveMemory.Write(CDMsg.CopyDataStruct^.lpData^,CDMsg.CopyDataStruct^.cbData);
    FReceiveMemory.Position := 0;
    FDataKind := CDMsg.CopyDataStruct^.dwData;
    MsgReceived(CDMsg);
  end else
    with Message do
      Result := CallWindowProc(@DefWindowProc,FAllocWnd,Msg,wParam,lParam);
end;

function TSendData.AllocMsgWnd: HWND;
var
  DummyClass: TWndClass;
begin
  FillChar(DummyClass,SizeOf(TWndClass),#0);
  DummyClass.hInstance := hInstance;
  DummyClass.lpfnWndProc := @DefWindowProc;
  DummyClass.lpszClassName := PChar(FUniqueName);
  Windows.RegisterClass(DummyClass);

  Result := CreateWindowEx(WS_EX_TOOLWINDOW, DummyClass.lpszClassName,
    '', WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
  SetWindowLong(Result,GWL_WNDPROC,LongInt(MakeObjectInstance(WndMethod)));
end;

procedure TSendData.MsgReceived(var CDMsg: TWMCopyData);
begin
  if Assigned(FOnMessage) then FOnMessage(self);
end;

function GetClassNameStr(hWindow:HWND):string;
var
  p:PChar;
  ret:integer;
begin
  GetMem(p,100);
  ret := GetClassName(hWindow,p,100);
  SetString(result,p,ret);
  FreeMem(p);
end;

function EnumReceiver(var WA:TReceiverWndArray;ClassName:string):integer;
var
  hWindow:HWND;
begin
  WA[0] := 0;
  hWindow := GetWindow(Application.Handle,GW_HWNDFIRST);
  while (hWindow <> 0) do begin
    if (UpperCase(GetClassNameStr(hWindow)) = UpperCase(ClassName)) then
    begin
      WA[0] := WA[0]+1;
      WA[WA[0]] := hWindow
    end;
    hWindow := GetWindow(hWindow,GW_HWNDNEXT);
  end;
  result := WA[0];
end;

procedure TSendData.SendData;
var
  CDS:TCopyDataStruct;
  hReceiver:TReceiverWndArray;
  i,numReceiver:integer;
begin
  CDS.dwData := FDataKind;
  CDS.cbData := FSendMemory.Size;
  CDS.lpData := FSendMemory.Memory;
  numReceiver := EnumReceiver(hReceiver,FUniqueName);
  if numReceiver> 0 then begin
    for i := 1 to numReceiver do
      SendMessage(hReceiver[i],WM_COPYDATA,FAllocWnd,LPARAM(@CDS));
  end;
end;

end.

ここでは、文字列の受け渡しのみ特別なインタフェースである Str プロパティーを通して送受信に便利に使えるようにしている。それ以外の一般的なデータは、送信時には、SendMemory にセットし、受信時には ReceiveMemory から読みとる。どちらも TMemoryStream のインスタンスであるから、その内容にアクセスする便利なメソッドが使える。特別なデータ形式を頻繁にやりとりするときは、このコンポーネントを改造して、Str プロパティーのような、その形式の送受信のために特別なプロパティーを追加すると便利につかえるだろう。

n01206.jpg

このコンポーネントをテストするプログラムを以下に示す。右図のように TEdit を二つ、TButton を二つ、TScrollBoxを一つとそのなかに TImageTOpenPictureDialog と今作った TSendData を置く。そして以下のようにする。SendData1UniqueName プロパティーにはオブジェクトインスペクタを使って適当な「呪文」の文字列をセットしておく。

List 8

unit Main;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  SendDataUnit, ExtDlgs, ExtCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    ScrollBox1: TScrollBox;
    Edit2: TEdit;
    Button1: TButton;
    Button2: TButton;
    Image1: TImage;
    OpenPictureDialog1: TOpenPictureDialog;
    SendData1: TSendData;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure SendData1Message(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses
  Jpeg;

type
  TDadaKind = (dkString,dkJpeg);

procedure TForm1.FormCreate(Sender: TObject);
begin
  Image1.Left := 0;
  Image1.Top := 0;
  Image1.AutoSize := true;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SendData1.Str := Edit2.Text;
  SendData1.DataKind := Ord(dkString);
  SendData1.SendData;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  OpenPictureDialog1.Filter := 'Jpeg files (*.jpg)|*.jpg';
  if OpenPictureDialog1.Execute then begin
    SendData1.SendMemory.Clear;// 前データをクリアする(必須)
    SendData1.SendMemory.LoadFromFile(OpenPictureDialog1.FileName);
    SendData1.DataKind := Ord(dkJpeg);
    SendData1.SendData;
  end;
end;

procedure TForm1.SendData1Message(Sender: TObject);
var
  JPG:TJpegImage;
begin
  case SendData1.Datakind of
    Ord(dkString):Edit1.Text := SendData1.Str;
    Ord(dkJPeg):begin
      JPG := TJpegImage.Create;
      try
        JPG.LoadFromStream(SendData1.ReceiveMemory);
        Image1.Picture.Assign(JPG);
      finally
        JPG.Free;
      end;
    end;
  end;
end;

end.
n01207.jpg

コンパイルしてできた実行ファイルを4つ起動しておく。そして、そのどれかの Edit2 に適当な文字列を入れてから、Button1 を押すと、すべてのインスタンスの Edit1 の文字列がその文字列に変更される。Button2 を押して適当な Jpeg ファイルを選ぶと、すべてのインスタンスの Image1 にその画像が表示される。これらを繰り返し行っても正常に動作する事が確かめられる。

ここまでで、文字列や LoadFromStream() を持つクラスの場合のみのデータの送受信を試してきた。そのほかの任意のデータや固定長のレコード型の配列などは、TMemoryStream クラスの Write/Read メソッドや WriteBuffer/ReadBuffer メソッドなどで読み書きできる。可変長レコード型配列の場合については、TWriterTReader を使ってストリームに読み書きする例が API with VCL の 第4章 汎用リストクラス にあるので参考にしてください。

以上にように、WM_COPYDATA メッセージを使って TMemoryStream を仲介することにより、任意のメモリブロックを複数のプロセス間で受け渡しすることができた。

redball.gif目次へ戻る