Delphi Programming / Object Pascal
[掲載 2005年09月06日] [更新 2014年03月25日] Delphi サンプルプログラム集
650_他のアプリへデータ ( 文字列や画像 ) の送信 ( Delphi 同士 )
動作確認等
Windows 7 U64(SP1) + Delphi XE Pro
650_SendText.zip [3,466 KB] 2014年03月25日版 (EXE 同梱)
2008年01月20日
送信文字列数の設定間違いを修正
2012年05月04日
Delphi 2009 以降対応のコードに修正
画像等、汎用性のある方法のサンプルを追加
記事の内容に合わせてページタイトルを変更
2014年03月25日
UIPI (ユーザーインタフェイス特権の分離の機能) 対応のサンプルを追加
他のアプリの操作の例です。
ここでの他のアプリとは、同じ Delphi で、しかも開発者自身がコーディング可能なものとします。
一方のアプリから他のアプリの操作を行います。他のアプリの操作と言ってもいろいろあります。その内、文字列や画像等の送信のテストです。WM_COPYDATA メッセージを使用します。
一般に、他のアプリを操作するには、Delphi 製のアプリに限らず、操作対象となるボタンやエディット等のコントロールのウィンドウハンドルの値を取得しなければなりません。ウィンドウハンドルを持つコントロールを一般に「ウィンドウ」と呼んでいます。コントロールの中にはウィンドウハンドルを持たないものもあります。それらは基本的に外部からの操作はできません。
ウィンドウハンドルは、そのコントロールを一意に識別するものです。一意に識別できなければ、操作対象を特定できません。そのためにウィンドウハンドルの取得が必要なわけです。ウィンドウハンドルの値は、そのアプリが起動するたびに Windows が割り当てますが、一度起動すれば一定の値です。
アプリケーションのウィンドウは [645_ ウィンドウの階層のツリー表示] で示すように、階層構造となっています。この中から目的のウィンドウを探し出します。FindWindow 等、Windows の API 関数を利用することになります。
ウィンドウハンドルが取得できたら、そのウィンドウに対して、ボタンクリックや文字列、テキストの送信、あるいは文字列取得のためのメッセージを送ります。例えば、ボタンクリックは、「マウスの左ボタンを押して放す」という操作ですから、下のコードように、その操作用のメッセージを送ります。
ボタンのクリックは、BM_CLICK というメッセージでも可能です。
リスト1
他のアプリの操作の例
マウスの左ボタンクリックを送る
copy code
// =============================================================================
// 他のアプリのボタンをこのアプリからクリックする
// hWindowは操作対象のアプリのボタンのウィンドウハンドルとする
// =============================================================================
procedure TSenderForm.Button1Click(Sender: TObject);
var
hWindow : HWND;
begin
// 何らかの方法で他のアプリの対象となるボタンのウィンドウハンドルhWindowを取得
// そこにマウスボタン操作のコードを送信する
SendMessage(hWindow, WM_LBUTTONDOWN, 0 , 0 );
SendMessage(hWindow, WM_LBUTTONUP, 0 , 0 );
end ;
ボタンの例のように、操作対象のコントロールによっては、専用のメッセージが用意されている場合もあります。どのようなメッセージを使用すべきかは、ケースバイケースになります。
ただし、全てのコントロールの操作が可能というわけではありません。それは、コントロールやメッセージの種類、あるいは Windows のバージョンによっても違います。
Windows Vista, 7 ではユーザーインターフェイス特権の分離 (UIPI) も関係してきます。また、「他のアプリからの操作を禁止」するアプリケーションを開発する場合があります。そのようなアプリケーションに対しては操作できないことになります。
ウィンドウメッセージは、自アプリの操作にも使用されます。Delphi の各コントロール、コンポーネントのソースコード (VCL のソースコード) を見ると、随所でウィンドウメッセージが使用されていることを確認できます。
同じ Delphi のアプリ同志の文字列送信の例です。この詳しい説明は Halbow 資料館にありますが、このサンプルは、それを最も簡単にしたものです。同梱の Sender.dpr と Receiver.dpr をコンパイルして作成した以下の実行ファイルを使用します。
Sender.exe 文字列を送るアプリ (この Edit1 のテキストを送る)
Receiver.exe 文字列を受取るアプリ
このテストプログラムでは、分かりやすいように、このような名前のプログラムとしていますが、予約語等の名前は、原則使用しない方が無難です。
図1
設計時画面
図2
両方のアプリを起動
図3
[Button1] クリック
リスト2
WM_COPYDATA メッセージによる他のアプリへの文字列送信
送信側のアプリのコード
copy code
unit SenderUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TSenderForm = class (TForm)
Button1: TButton;
Edit1: TEdit;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
SenderForm: TSenderForm;
implementation
{$R *.DFM}
// =============================================================================
// Button1をクリックするとEdit1の文字列をReceiveFormに送る
//
// COPYDATASTRUCT構造体
// dwData LongInt 送信する32ビット値
// cdData Longint lpDataのサイズ(バイト数)
// lpData LongInt 送信データのポインタ(0も可)
//
// このメッセージを送るときはSendMessageを使用する(PostMessageは使用しない)
// データを受取るアプリで受取ることのできないオブジェクトのポインタを送っては
// いけない。
// =============================================================================
procedure TSenderForm.Button1Click(Sender: TObject);
var
hWindow : HWND;
CDS : TCopyDataStruct;
StrSend : String ;
begin
// 受取る側のウィンドウハンドル
hWindow := FindWindow('TReceivForm' , nil );
if hWindow = 0 then exit;
StrSend := Edit1.Text;
CDS.dwData := 1234 ; // 適当な値
CDS.cbData := (Length(StrSend) + 1) * SizeOf(Char);
CDS.lpData := Pointer(StrSend);
SendMessage(hWindow, WM_COPYDATA, Self.Handle, LPARAM(Addr(CDS)));
end ;
end .
リスト3
WM_COPYDATA メッセージで文字列を受信する側のアプリのコード
copy code
unit ReceiverUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TReceivForm = class (TForm)
Edit1: TEdit;
Label1: TLabel;
protected
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
ReceivForm: TReceivForm;
implementation
{$R *.DFM}
// =============================================================================
// 同じフォルダにあるSender.exeのButton1をクリックすると、このメッセージで文字
// 列を受信して、Edit1にその文字列をセットする
//
// COPYDATASTRUCT構造体
// dwData LongInt 受信する32ビット値
// cdData Longint lpDataのサイズ(バイト数)
// lpData LongInt 受信データのポインタ(0も可)
// =============================================================================
procedure TReceivForm.WMCopyData(var Message: TWMCopyData);
var
S : String ;
begin
if Message.CopyDataStruct.dwData = 1234 then begin
S := PChar(Message.CopyDataStruct.lpData);
Edit1.Text := S;
end ;
end ;
end .
レコード型の場合は、送信側と受信側で、サイズと型を一致させておく必要があります。したがって、文字列はサイズをあらかじめ決めておきます。
図4
設計時画面
図5
両方のアプリを起動
図6
[Button1] をクリック
他のアプリに文字列が送信される
受信側の、他のアプリでは、受信した内容を TMemo に表示
リスト4
他のアプリにレコード型のデータを送信する側のアプリのコード
copy code
unit SenderUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TSenderForm = class (TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
SenderForm: TSenderForm;
implementation
{$R *.DFM}
// =============================================================================
// Button1をクリックするとMyDataをReceiveFormに送る
// TPersonDataのサイズと型は受信側と同じにしておく
// =============================================================================
type
TPersonData = record
Name : array [0 ..49 ] of Char;
Age : Integer;
Weight : Integer;
Birthday : TDateTime;
end ;
procedure TSenderForm.Button1Click(Sender: TObject);
var
MyData : TPersonData;
hWindow : HWND;
CDS : TCopyDataStruct;
begin
MyData.Name := '出瑠 譜愛' ;
MyData.Age := 20 ;
MyData.Weight := 58 ;
MyData.Birthday := StrToDate('2005/9/6' );
// 受取る側のウィンドウハンドル
hWindow := FindWindow('TReceivForm' , nil );
if hWindow = 0 then exit;
CDS.dwData := 1234 ; // 適当な値
CDS.cbData := SizeOf(MyData);
CDS.lpData := Addr(MyData);
SendMessage(hWindow, WM_COPYDATA, Self.Handle, LPARAM(Addr(CDS)));
end ;
end .
リスト5
レコード型のデータを受信する側アプリのコード
copy code
unit ReceiverUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TReceivForm = class (TForm)
Label1: TLabel;
Memo1: TMemo;
protected
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
private
{ Private 宣言 }
public
{ Public 宣言 }
end ;
var
ReceivForm: TReceivForm;
implementation
{$R *.DFM}
// =============================================================================
// Momo1に受信データを表示
// TPersonDataは送信側とデータのサイズと型を一致させておく
// =============================================================================
type
TPersonData = record
Name : array [0 ..49 ] of Char;
Age : Integer;
Weight : Integer;
Birthday : TDateTime;
end ;
procedure TReceivForm.WMCopyData(var Message: TWMCopyData);
var
MyData : TPersonData;
begin
if Message.CopyDataStruct.dwData = 1234 then begin
// ポインタの逆参照
MyData := TPersonData(Message.CopyDataStruct^.lpData^);
Memo1.Clear;
Memo1.Lines.Add(MyData.Name);
Memo1.Lines.Add(IntToStr(MyData.Age));
Memo1.Lines.Add(IntToStr(MyData.Weight));
Memo1.Lines.Add(FormatDateTime('yyyy"年"mm"月"dd"日"' , MyData.BirthDay));
end ;
end ;
end .
Halbow さんが、これまでのサンプルと同じ動作を行うための、メッセージプロセス間通信コンポーネントを作成しています。そのコンポーネントを使用した例です。
このコンポーネントを、Delphi 2009 以降でも利用可能にするための修正をして添付しています。修正内容はソースコードで確認してください。
このサンプルプログラムには、送信側、受信側のアプリの区別はありません。全て同じプログラムです。これを複数起動してテストします。このコンポーネントの、DataKind プロパティと、SendData メソッドの引数を利用すると、送信側、受信側の動作を制御できます。
図7
設計時画面
図8
複数起動してテスト
図9
[テキスト送信] ボタンをクリック
図10
[画像送信] ボタンをクリック
ダイアログで JPEG 画像を選択
選択した画像が他アプリにも送られる
その画像が他のアプリにも表示される
リスト6
プロセス間通信コンポを使用して、他のアプリにデータを送信または受信
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtDlgs, ExtCtrls, StdCtrls, plSendDataUnit;
type
TForm1 = class (TForm)
Edit1: TEdit;
ScrollBox1: TScrollBox;
Button1: TButton;
Button2: TButton;
Image1: TImage;
OpenPictureDialog1: TOpenPictureDialog;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
InitDir : String ;
plSendData1: TplSendData;
procedure OnSendDataMessage(Sender: TObject; hSendWnd: HWND);
public
{ Public 宣言 }
end ;
var
Form1: TForm1;
implementation
{$R *.DFM}
uses Jpeg;
// -----------------------------------------------------------------------------
// plSendDataコンポのDataKindプロパティは、いろいろな制御フラグとして利用可能
// ここでは、受け渡しのデータの種類とその列挙型を定義
// -----------------------------------------------------------------------------
type
TDadaKind = (dkString, dkJpeg);
// =============================================================================
// TplSendDataコンポを生成して、そのイベントを定義
// 画像表示用のTImageの設定
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
plSendData1 := TplSendData.Create(Self);
plSendData1.OnMessage := OnSendDataMessage;
Image1.Left := 0 ;
Image1.Top := 0 ;
Image1.AutoSize := True;
end ;
// =============================================================================
// [テキスト]ボタン
// Edit1に入力した文字列を他のアプリに送信
// =============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
plSendData1.Str := Edit1.Text;
plSendData1.DataKind := Ord(dkString);
plSendData1.SendData(Handle);
end ;
// =============================================================================
// [画像]ボタン
// [画像]ボタンで読み込んだ画像(JPG)を他のアプリに送信
// =============================================================================
procedure TForm1.Button2Click(Sender: TObject);
begin
OpenPictureDialog1.Filter := 'Jpeg files (*.jpg)|*.jpg' ;
if InitDir = '' then begin
OpenPictureDialog1.InitialDir := ExtractFileDir(Application.ExeName);
end else begin
OpenPictureDialog1.InitialDir := InitDir;
end ;
if OpenPictureDialog1.Execute then begin
InitDir := ExtractFileDir(OpenPictureDialog1.FileName);
// 前データをクリアする(必須)
plSendData1.SendMemory.Clear;
plSendData1.SendMemory.LoadFromFile(OpenPictureDialog1.FileName);
plSendData1.DataKind := Ord(dkJpeg);
plSendData1.SendData(Handle);
end ;
end ;
// =============================================================================
// TplSendDataコンポのイベント
// このメッセージからデータを取出したり、送信したりする
//
// このサンプルでは、自アプリのEdit1のテキストは更新しない
// 読み込んだ自アプリのJPEG画像は、このイベントで表示する
// つまり、自分自身に送信する
// =============================================================================
procedure TForm1.OnSendDataMessage(Sender: TObject; hSendWnd: HWND);
var
JPG : TJpegImage;
begin
case plSendData1.Datakind of
Ord(dkString):
begin
if hSendWnd <> Handle then Edit1.Text := plSendData1.Str;
end ;
Ord(dkJPeg):
begin
JPG := TJpegImage.Create;
try
JPG.LoadFromStream(plSendData1.ReceiveMemory);
Image1.Picture.Assign(JPG);
finally
JPG.Free;
end ;
end ;
end ;
end ;
end .
本ページのこれまでのサンプルは、受信側のアプリケーションを管理者権限で実行し、送信側のアプリを一般ユーザで起動した場合、受信側でデータは受信できません。Delphi の IDE は管理者権限で実行します。したがって、デバッグ時も受信できないことになります。
これは、ユーザーインタフェイス特権の分離の機能 (UIPI) によるものです。
次のサンプルは、上のサンプルに UIPI 対応のコードを追加して、WM_COPYDATA メッセージを受信可能にしています。WM_COPYDATA メッセージの受信を可能にするために、ChangeWindowMessageFilter 関数を使用しています。
このコードの追加によって、受信側のアプリケーションを管理者権限で実行しても、データが受信できるようにしています。コードの動作内容については、以下の記事を参考にしてください。
[備考]
[000] [04] のコードでは、user32.dll を実行時にロードしています。そして、dll 内の関数の存在をチェックしています。これを dll の動的リンクと言っています。
一方、下のコードでは user32.dll 内の関数をアプリ開始時にロードしています。これを dll の静的リンクと言っています。したがって、ChangeWindowMessageFilter という関数がない Windows XP で実行すると、関数が存在しないというエラーが発生して、プログラムは実行できません。
リスト7
プロセス間通信コンポを使用して、他のアプリにデータを送信または受信
管理者権限で実行しても、送信側からのデータを受信可能にしている
Form1 の OnCreate イベント以外は上のサンプルと同じなので省略
copy code
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtDlgs, ExtCtrls, StdCtrls, plSendDataUnit;
type
TForm1 = class (TForm)
Edit1: TEdit;
ScrollBox1: TScrollBox;
Button1: TButton;
Button2: TButton;
Image1: TImage;
OpenPictureDialog1: TOpenPictureDialog;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private 宣言 }
InitDir : String ;
plSendData1: TplSendData;
procedure OnSendDataMessage(Sender: TObject; hSendWnd: HWND);
public
{ Public 宣言 }
end ;
function ChangeWindowMessageFilter(msg: Cardinal; dwFlag: DWORD): BOOL;
stdcall ; external 'user32.dll' ; stdcall ;
var
Form1: TForm1;
implementation
{$R *.DFM}
uses Jpeg;
// -----------------------------------------------------------------------------
// plSendDataコンポのDataKindプロパティは、いろいろな制御フラグとして利用可能
// ここでは、受け渡しのデータの種類とその列挙型を定義
// -----------------------------------------------------------------------------
type
TDadaKind = (dkString, dkJpeg);
// =============================================================================
// TplSendDataコンポを生成して、そのイベントを定義
// 画像表示用のTImageの設定
// =============================================================================
procedure TForm1.FormCreate(Sender: TObject);
const
MSGFLT_ADD = 1 ;
MSGFLT_REMOVE = 2 ;
WM_COPYGLOBALDATA = $0049 ;
begin
plSendData1 := TplSendData.Create(Self);
plSendData1.OnMessage := OnSendDataMessage;
Image1.Left := 0 ;
Image1.Top := 0 ;
Image1.AutoSize := True;
// UIPI対応コード
// 本アプリでWM_COPYDATAメッセージの受信を可能にする
ChangeWindowMessageFilter(WM_COPYDATA, MSGFLT_ADD);
end ;