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
他のアプリの操作の例
マウスの左ボタンクリックを送る
//=============================================================================
//  他のアプリのボタンをこのアプリからクリックする
//  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 のソースコード) を見ると,随所でウィンドウメッセージが使用されていることを確認できます.





01_文字列の送信


同じ Delphi のアプリ同志の文字列送信の例です.この詳しい説明は Halbow 資料館にありますが,このサンプルは,それを最も簡単にしたものです.同梱の Sender.dpr と Receiver.dpr をコンパイルして作成した以下の実行ファイルを使用します.
  • Sender.exe        文字列を送るアプリ (この Edit1 のテキストを送る)
  • Receiver.exe      文字列を受取るアプリ
このテストプログラムでは,分かりやすいように,このような名前のプログラムとしていますが,予約語等の名前は,原則使用しない方が無難です.


図1
設計時画面
  • 右が自アプリで,送信側
  • 左が他のアプリで,受信側
図2
両方のアプリを起動
図3
[Button1] クリック
  • 他のアプリに文字列が送信される

リスト2
WM_COPYDATA メッセージによる他のアプリへの文字列送信
送信側のアプリのコード
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 メッセージで文字列を受信する側のアプリのコード
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.




02_レコード型の場合


レコード型の場合は,送信側と受信側で,サイズと型を一致させておく必要があります.したがって,文字列はサイズをあらかじめ決めておきます.


図4
設計時画面
  • 右が自アプリで,送信側
  • 左が他のアプリで,受信側
図5
両方のアプリを起動
図6
[Button1] をクリック
  • 他のアプリに文字列が送信される
  • 受信側の,他のアプリでは,受信した内容を TMemo に表示

リスト4
他のアプリにレコード型のデータを送信する側のアプリのコード
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
レコード型のデータを受信する側アプリのコード
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.




03_画像等,汎用的な方法


Halbow さんが,これまでのサンプルと同じ動作を行うための,メッセージプロセス間通信コンポーネントを作成しています.そのコンポーネントを使用した例です.
このコンポーネントを,Delphi 2009 以降でも利用可能にするための修正をして添付しています.修正内容はソースコードで確認してください.

このサンプルプログラムには,送信側,受信側のアプリの区別はありません.全て同じプログラムです.これを複数起動してテストします.このコンポーネントの,DataKind プロパティと,SendData メソッドの引数を利用すると,送信側,受信側の動作を制御できます.


図7
設計時画面
図8
複数起動してテスト
  • 左端のアプリの TEdit に文字列を入力
図9
[テキスト送信] ボタンをクリック
  • 他アプリの TEdit に文字列がセットされる
図10
[画像送信] ボタンをクリック
  • ダイアログで JPEG 画像を選択
  • 選択した画像が他アプリにも送られる
  • その画像が他のアプリにも表示される

リスト6
プロセス間通信コンポを使用して,他のアプリにデータを送信または受信
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.




04_画像等,汎用的な方法 - UIPI対応


本ページのこれまでのサンプルは,受信側のアプリケーションを管理者権限で実行し,送信側のアプリを一般ユーザで起動した場合,受信側でデータは受信できません.Delphi の IDE は管理者権限で実行します.したがって,デバッグ時も受信できないことになります.
これは,ユーザーインタフェイス特権の分離の機能 (UIPI) によるものです.

次のサンプルは,上のサンプルに UIPI 対応のコードを追加して,WM_COPYDATA メッセージを受信可能にしています.WM_COPYDATA メッセージの受信を可能にするために,ChangeWindowMessageFilter 関数を使用しています.
このコードの追加によって,受信側のアプリケーションを管理者権限で実行しても,データが受信できるようにしています.コードの動作内容については,以下の記事を参考にしてください.

[備考]
[000] [04] のコードでは,user32.dll を実行時にロードしています.そして,dll 内の関数の存在をチェックしています.これを dll の動的リンクと言っています.
一方,下のコードでは user32.dll 内の関数をアプリ開始時にロードしています.これを dll の静的リンクと言っています.したがって,ChangeWindowMessageFilter という関数がない Windows XP で実行すると,関数が存在しないというエラーが発生して,プログラムは実行できません.


リスト7
プロセス間通信コンポを使用して,他のアプリにデータを送信または受信
管理者権限で実行しても,送信側からのデータを受信可能にしている
Form1 の OnCreate イベント以外は上のサンプルと同じなので省略
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;