ホーム >プログラム >Delphi 6 ローテクTips >二重起動防止+ウィンドウを前面に+先に起動したプログラムへ引数を渡す

プログラムの二重起動防止、
起動済みのプログラムを前面に表示させ、
先に起動したプログラムへ後から起動したプログラムの引数を渡しましょうというページ。



二重起動防止
program test;

uses
  Windows,
  Messages,
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

begin
  //二重起動チェック
  if  (CreateMutex(nil, False, 'Dsapbi203dap') <> 0)  //関数が成功して
  and (GetLastError = ERROR_ALREADY_EXISTS)            //既に同名のミューテックスオブジェクトがあれば
  then begin                                          //二重起動である
    //メッセージを出す
    Application.MessageBox('二重起動は許可されていません', 'メッセージ');
    //プロラム終了
    Exit;
  end;

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
Delphiで二重起動を防止するにはプロジェクトファイル(*.dpr)にコードを記述します。
FindWindow APIを使う方法もありますがここではCreateMutex APIを使います。

CreateMutexの説明を読んでみると、何やら良く分からないことがいっぱい書いてあります。
がんばって理解しようと最後まで読みきろうと思っても、もう何が何だかで眠くなってきてしまいます。
けれどもこの場合大事なのは という点です。
第三引数にこのプログラムと他のプログラムを区別できるようにユニークな文字列を指定しておきます。
「\」以外の英数記号を大文字小文字取り混ぜて適当にキーボードから打てば良いでしょう。
CreateMutex(nil, False, 'Dsapbi203dap')

あとは二重起動であればメッセージを出し、Exitでプログラムを終了させればOKです。
先に起動しているプログラムを前面に表示
program test;

uses
  Windows,
  Messages,
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.res}

var
  lh_Window, lh_Owner: HWND;
begin
  //二重起動チェック
  if  (CreateMutex(nil, False, 'Dsapbi203dap') <> 0)  //関数が成功して
  and (GetLastError = ERROR_ALREADY_EXISTS)            //既に同名のミューテックスオブジェクトがあれば
  then begin                                          //二重起動である
    //まずメッセージを出す
    Application.MessageBox('二重起動は許可されていません', 'メッセージ');

    //先に起動しているこのプログラムのウィンドウハンドルとオーナーウィンドウのハンドルを取得
    lh_Window := FindWindow('TForm1', nil);
    lh_Owner  := GetWindow(lh_Window, GW_OWNER);
    if (IsIconic(lh_Owner)) then begin
      //最小化されていたら元のサイズに戻す
      SendMessage(lh_Owner, WM_SYSCOMMAND, WPARAM(SC_RESTORE and $FFF0), 0);
    end else begin
      //前面に移動させる
      SetForegroundWindow(lh_Window);
    end;
    Exit;  //プロラム終了
  end;

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.
二重起動を検出できたら次は先に起動しているプログラムを前面に表示するようにしてみます。
  lh_Window := FindWindow('TForm1', nil);
  lh_Owner  := GetWindow(lh_Window, GW_OWNER);
  if (IsIconic(lh_Owner)) then begin
    //最小化されていたら元のサイズに戻す
    SendMessage(lh_Owner, WM_SYSCOMMAND, WPARAM(SC_RESTORE and $FFF0), 0);
  end else begin
    //前面に移動させる
    SetForegroundWindow(lh_Window);
  end;
先に起動したプログラムを前面に移動させるにはSetForegroundWindow APIを使います。
前面に表示させたいフォームのウィンドウハンドルを引数に指定します。

フォームのウィンドウハンドルはFindWindow APIで第一引数にクラス名(フォームのオブジェクトインスタンスの型名)、第二引数にウィンドウ名(フォームのCaptionプロパティ)を指定して取得します。
オブジェクトインスタンスの型とはClassNameプロパティのことでフォームの場合はNameプロパティの頭に'T'のついたものになります。
Form1ならTForm1、Form2ならTForm2、App_MessageReceiverならTApp_MessageReceiverなどとなります。
第二引数のウィンドウ名はフォームのタイトル(Captionプロパティ)が変わることが予想される場合にはnilを指定します

  FindWindow('TForm1', nil)

クラス名はできるだけユニークなものが探しやすくてよいと思います。
この例ではTForm1というありきたりなものを使っていますが…
オブジェクトインスペクタでNameプロパティに他と区別しやすい名前をつけておくとよいでしょう。
またDelphiでIDEを表示させていると同じクラス名のフォームがあるのでそちらがひっかかってしまうことがあります。
テストのときはDelphiを最小化したりして対策します。
対策済みの解決法はこちら

Delphiで作ったプログラムの場合、プログラムがアイコン化しているかを調べたりアイコン化していたプログラムを元のサイズに戻すにはフォームのハンドルではなくオーナーウィンドウ(Application)のハンドルを使います。
オーナーウィンドウのハンドルはGetWindow APIで第一引数にフォームのハンドルをセットし、第二引数にGW_OWNERを指定して取得します。
  lh_Window := FindWindow('TForm1', nil);
  //オーナーウィンドウのハンドルを取得
  lh_Owner  := GetWindow(lh_Window, GW_OWNER);
  //プログラムが最小化されているかテスト
  if (IsIconic(lh_Owner)) then begin
    //最小化されていたら元のサイズに戻す
    SendMessage(lh_Owner, WM_SYSCOMMAND, WPARAM(SC_RESTORE and $FFF0), 0);
SendMessageではなくShowWindow APIを使っても戻せますが、その場合DelphiのApplicationEventsコンポーネントのOnRestoreイベントが発生しません。
先に起動しているプログラムへ引数を渡す
ついでなので先に起動しているプログラムに後から起動したプログラムの引数を渡すことをやってみます。
色々なやり方があるようですが、フォームのCaptionプロパティを利用するのが分かりやすくて良いのではと思います。

処理の流れは
  1. 先に起動しているプログラムはCaptionが変更されたことをメッセージで知ることができるようにしておく。
  2. 後から起動したプログラムは先に起動しているプログラムのフォームのCaptionプロパティに自身の引数をセットする。
  3. 先に起動しているプログラムは(後からのプログラムから引数をセットされた)Captionプロパティを取り出し内容に応じて処理を行う。

というものになります。

ウィンドウテキスト(FormのCaptionプロパティ)を変更してしまうのでメインフォームではなく通信専用のフォームを作っておくのがよいと思います。
他のコンポーネント(TEditやTButtonなど)はフォームと違いクラス名はNameプロパティの値にかかわらずTEditやTButtonのままです。
フォームを通信のレシーバーとして利用しているのはそのためです。

program test;

uses
  Windows,
  Messages,
  Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form_Message};
{$R *.res}

var
  lh_Message, lh_Window, lh_Owner: HWND;
begin
  //二重起動チェック
  if  (CreateMutex(nil, False, 'Dsapbi203dap') <> 0)  //関数が成功して
  and (GetLastError = ERROR_ALREADY_EXISTS)            //既に同名のミューテックスオブジェクトがあれば
  then begin                                          //二重起動である
    lh_Message := FindWindow('TForm_Message', nil); //第二引数はnil
    if (ParamCount > 0) and (lh_Message <> 0) then begin
      //先に起動しているプログラムに引数を渡す
      SendMessage(lh_Message, WM_SETTEXT, 0, LPARAM(CmdLine));
    end else begin
      //まずメッセージを出す
      Application.MessageBox('二重起動は許可されていません', 'メッセージ');

      //先に起動しているこのプログラムのウィンドウハンドルとオーナーウィンドウのハンドルを取得
      lh_Window := FindWindow('TForm1', nil);
      lh_Owner  := GetWindow(lh_Window, GW_OWNER);
      if (IsIconic(lh_Owner)) then begin
        //最小化されていたら元のサイズに戻す
        SendMessage(lh_Owner, WM_SYSCOMMAND, WPARAM(SC_RESTORE and $FFF0), 0);
      end else begin
        //前面に移動させる
        SetForegroundWindow(lh_Window);
      end;
    end;
    Exit;  //プロラム終了
  end;

  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TForm_Message, Form_Message);
  Application.Run;
end.
ウィンドウテキストをセットするのにはSetWindowText APIではなくSendMessageを使います。
SetWindowTextだとウィンドウテキストはちゃんとセットされるのですがWM_SETTEXTメッセージを補足できないのか、以下のコードでいうとTForm_Message.WMSetTextプロシージャが実行されません。
SendMessageであればどちらもちゃんとできます。
unit Unit2;

interface

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

type
  TForm_Message = class(TForm)
  private
    { Private 宣言 }
    procedure WMSetText(var Msg: TMessage); message WM_SETTEXT; //メッセージの受信
  public
    { Public 宣言 }
  end;

var
  Form_Message: TForm_Message;

implementation

{$R *.dfm}

//後から起動したこのプログラムからのメッセージを受けとる
procedure TForm_Message.WMSetText(var Msg: TMessage);
begin
  inherited;
  {ここに処理内容を記述}

  //とりあえずビープを鳴らして表示。
  //タイトルバーのテキストが変わっていることを確認

  Beep;
  Show;
end;

end.
ちなみにD6では(多分2007までも)Unicode文字(WideString)をSendMessageWを使って渡そうとしてもAnsiStringに変換されてしまいます。
TFormがUnicode対応でないのでいたしかたなしというところのようです。
どうしてもUnicode文字を渡したいのであればWideCharToMultiByteでWideStringをUTF-7に変換して渡し、受け取る側で UTF-7 をWideStringに戻して使うというようなやり方になると思います。

2010-02-19:FindWindowでクラス名にForm1.ClassNameなどとしていたためにエラーになっていた不具合を修正。
2008-04-16