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 を仲介することにより、任意のメモリブロックを複数のプロセス間で受け渡しすることができた。