API の稿の 第17章 メモリ操作 で説明したように、Win32 におけるメモリの仮想アドレス空間はプロセスごとに独立している。したがって、何かの情報をメモリに確保してそれを他のプロセスに渡したいとき、そのアドレスを教えてやっても、受け取る方のプロセスにとってそのアドレスは無意味なものになっている。通常はアクセス違反を起こしてどちらかのアプリが強制終了する。Win32 におけるプロセス間通信には、DDE、OLE、パイプ、クリップボード、メールスロットなどがあるらしいが、クリップボード以外では、わたしは Delphi のDDEについて、Delphi2 のときに試したことがあるだけで、ほかの方法についてはやったことがない。ここでは、メッセージを使って任意のメモリブロックをプロセス間でやりとりする方法を調べよう。
メッセージは、メッセージIDそのものがシステムグローバルであり、また、通信するプロセスのウィンドウハンドルもシステムレベルでユニークさが保証されている。したがって、プロセス間でメッセージをウィンドウをつかってやりとりすること自体には、何の問題もない。しかし、やりとりできる情報は、ID 以外には wParam、lParam、それから SendMessage() の戻り値の result の三つの32ビットの数値に限られる。したがって、普通にメッセージをやりとりするだけでは、任意のデータを受け渡しすることはできない。ここの メッセージ受信専用ウィンドウ のところでやったように、文字列については、WM_GETTEXT と WM_SETTEXT メッセージを使って受け渡しができる。これらは、ウィンドウテキストがウィンドウの属性の一つなので、ウィンドウズが仮想メモリ空間のアドレスの問題を自動的に解決してくれている例外的なメッセージなのである。しかし、これらのメッセージを使っても、文字列以外のデータを受け渡すことはできない。
OSが仮想メモリ空間のアドレスの問題を自動的に解決してくれている例外的なメッセージは、もう一つある。WM_COPYDATA というメッセージで、任意のメモリブロックをその先頭を指すポインタとサイズを指定して、異なるプロセスのウィンドウに送ると、受け取り先で問題なくメモリの内容のコピーができるのである。ここでは、この WM_COPYDATA メッセージを使って、任意のメモリブロックをプロセス間でやりとりすることを試してみよう。任意のメモリブロックには、文字列はもちろん、ビットマップやJPEGなどの画像や .wav などの音楽ファイルや、任意のレコード型の配列や可変長レコードなども読み書きできるので、ほとんどどんな形式のデータでもやりとりできるだろう。ここでは、TMemoryStream をデータのやりとりに使うバッファメモリとして利用する。そうすると、その Write() や Read() メソッドで読み書きができるし、可変長レコードの場合は TReader と TWriter クラスを利用できる。また、SaveToStream() や LoadFromStream() メソッドを持つクラスにはそのまま使えるだろう。
WM_COPYDATA は Delphi 付属の 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 画像を受け渡ししてみよう。Form1 に Button1 をおいて以下のようにする。
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.
実行すると右図のようになる。
ここでは、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 が送信先のウィンドウを見つけやすいように、Form1 の Name をオブジェクトインスペクタで 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.
これをプロジェクトファイルを Sender.dpr として保存する。このように 'TReceiverForm' のクラス名を使って送信相手のウィンドウを FindWindow() で見つけだしている。実行結果は右図のようになり、プロセス間でもJPEG画像の受け渡しがうまくいっていることを示している。
同じことを文字列の受け渡しの場合で試してみよう。Sender を以下に示す。Form1 に Memo1 と Button1 を置いて
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.Text を PChar にキャストして、その逆参照を Length(Memo1.Lines.Text)+1 のサイズでメモリストリームに書き込んで送信している。末尾のヌル文字もで書き込んでおくと文字列の長さを VCL の StrLen() 関数で求めることができる。一般に、書き込んだ内容のサイズとメモリストリームの 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.
このように、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;
を使って解決できる。これは、幅と高さがゼロのトップレベルウィンドウをつくるので EnumWindows() や GetWindow() API で複数の受信ウィンドウを見つけられる。ここでは API with VCL の 第6章 ウィンドウの階層について で作った WinSearch.pas の中の EnumWindowsOfClassNameAsZOrder() 関数を少し改造して使ってみよう。それでは具体的にやってみよう。右図のように、ScrollBox1 とその中に Image1 を置き、その外に Button1 と OpenPictureDialog1 を置いて以下のようにする。
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.
コンパイルしてできた実行ファイルをエクスプローラからダブルクリックして、複数のインスタンスを立ち上げる。右図のように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 プロパティーのような、その形式の送受信のために特別なプロパティーを追加すると便利につかえるだろう。
このコンポーネントをテストするプログラムを以下に示す。右図のように TEdit を二つ、TButton を二つ、TScrollBoxを一つとそのなかに TImage と TOpenPictureDialog と今作った TSendData を置く。そして以下のようにする。SendData1 の UniqueName プロパティーにはオブジェクトインスペクタを使って適当な「呪文」の文字列をセットしておく。
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.
コンパイルしてできた実行ファイルを4つ起動しておく。そして、そのどれかの Edit2 に適当な文字列を入れてから、Button1 を押すと、すべてのインスタンスの Edit1 の文字列がその文字列に変更される。Button2 を押して適当な Jpeg ファイルを選ぶと、すべてのインスタンスの Image1 にその画像が表示される。これらを繰り返し行っても正常に動作する事が確かめられる。
ここまでで、文字列や LoadFromStream() を持つクラスの場合のみのデータの送受信を試してきた。そのほかの任意のデータや固定長のレコード型の配列などは、TMemoryStream クラスの Write/Read メソッドや WriteBuffer/ReadBuffer メソッドなどで読み書きできる。可変長レコード型配列の場合については、TWriter と TReader を使ってストリームに読み書きする例が API with VCL の 第4章 汎用リストクラス にあるので参考にしてください。
以上にように、WM_COPYDATA メッセージを使って TMemoryStream を仲介することにより、任意のメモリブロックを複数のプロセス間で受け渡しすることができた。