バッチファイルからウィンドウをコントロールする方法がわかりたい

戻る

バッチファイルは、MS-DOSが全盛のころには、ちょっとした処理を行うためによく 利用していました。
しかし、Windows 上で動作するアプリケーションはウィンドウを操作して動作するもの がほとんどであるため、バッチファイルに記述して逐次処理を行えるものは少なく なっています。
このように、バッチファイルは AUTOEXEC.BAT 以外ではほとんど利用すること がなくなっているようですが、ファイルのバックアップや定期的に行う必要があり、 ユーザがカスタマイズする必要がある処理を行うときに、効果的に利用することが できます。
このような処理は、クライアントよりサーバで行われることが 多いため、処理の経過や結果をクライアントに通知する方法についても考えて みます。

9-1 バッチファイルとは

○バッチファイルは、なくならない
バッチファイルは、ユーザがデスクトップ上で操作する必要がない動作や、定期的 に実行しなければならない処理や繰り返して動作させる必要がある処理を行うときに 利用します。
このような処理はいくらウィンドウを操作して操作するアプリケーションが多くなった といっても完全になくなることはありません。
バッチファイルはコンパイラやインタプリタのような特別なツールを利用しなく ても利用できるため、保守が簡単でカスタマイズを容易に行うことができます。
そして、処理を制御するための簡単な制御構文(if, for など)を利用して簡単な プログラミングを行うことができます。

○バッチプログラミングで利用できるコマンド
バッチファイルで利用できるコマンドは、コマンドプロセッサである COMMAND.COM(Windows95)または CMD.EXE(WindowsNT) の内部で実行 される内部コマンドと、コンソールアプリケーションやMS-DOSアプリケーションの ような外部コマンドがあります。
外部コマンドは、例えばFORMAT.EXE のようにシステムにあらかじめ インストールされているものと、ユーザが作成したコンソールアプリケーション (COM, EXEファイル)や、バッチファイルがあります。
MS-DOSは、シングルタスクのOSであるため、各コマンドは必ず逐次実行されますが、 Windows95やWindowsNT では、バッチファイルから外部コマンドを並列に実行させる ことができます。

9-2 バッチファイルプログラミング

UNIXのshcshなどがもつシェルスクリプトでは、MS-DOSのバッチ ファイルよりかなり複雑なことができ、一見ファイルの中身がテキストなのか バイナリファイルなのかを区別することができません。また、シェルスクリプト以外 にもAWKやPerlなどインタプリタで動作する言語が広く利用されています。
しかし、MS-DOSの世界ではこのような実行形態の言語は、あまり普及していないよう です。
Windowsでバッチファイルを利用する場面は AUTOEXEC.BAT や WindowsNT に おけるログオン時の環境設定のように、コマンドを逐次実行で利用する以外に使う ことが少なくなってきています。
しかし、定期的に行うバックアップなど、わざわざツールを使うまでもないちょっと した処理や、環境変数を一時的に設定したいときなどに活用できます。

○外部コマンドの実行方法と環境変数領域の設定

バッチファイルから外部コマンドを実行するには、START コマンドを利用 します。START コマンドは、Windows95とWindowsNTでは、機能がかなり 異なりますので、利用するときには、
 START /?
でヘルプを参照して、動作をよく確認して利用してください。
環境変数を利用するバッチファイルで、環境変数領域不足を知らせるエラーが 発生したときには、
 command.com /e:2048
のように、環境変数領域を拡張する必要があります。バッチファイルのコマンドを、 表9-1に示します。


表9-1:バッチファイルのコマンド
引数 ファイル名 arg1 arg2 arg3 arg4 arg5 ...

%0にはファイル名
%1から%9に引数の文字列が設定される
%1=arg1, %2=arg2, %3=arg3 %4=arg4 %5=arg5 ...
変数 環境変数を %PATH% のように指定して利用できる

set DATA=文字列
echo %DATA%
set DATA=

echo 実行される行を表示状態を指定する (例 echo on | off)

例: @echo off

文字列を表示する

例: echo 文字列
for 処理を繰り返す
for %%変数 in (セット) do コマンド

例: for %%a in (*.c) do echo %%a
goto 指定されたラベル行にジャンプする
goto ラベル

例: goto exit
    :
:exit
    :
if 処理の分岐
if [not] errorlevel n コマンド

例: xcopy c:\work d:\work /s
  if errorlevel 2 goto abort
  if errorlevel 0 goto exit

if [not] str1 == str2 コマンド

例: if "%1" == "" goto exit
     :
:exit
     :

if [not] exist ファイル名 コマンド

例: if not exist "%1" goto exit
     :
:exit
     :
rem コメント
rem [コメント]

例: rem --- ○○処理 ---
pause 処理の中断
pause [メッセージ]

例: pause
choice 複数選択
choice [/C[:]選択肢] [/N] [/S] [/T[:]c,nn] [文字列]

例: choice /C:ABCD /T:B,10 選択する
 if errorlevel 4 goto D
 if errorlevel 3 goto C
 if errorlevel 2 goto B
 if errorlevel 1 goto A
shift パラメータの置き換え(パラメータを左シフトする)
shift

例: :loop
  copy %1 \data
  shift
  if not "%1" == "" goto loop
call 他のバッチファイルを呼び出す
call [ドライブ:][パス]ファイル名 [パラメータ]

例: call us


9-3 バッチファイルからウィンドウを操作する

○Windows95のバッチファイルは、16ビットアプリケーション
Windowsのアプリケーションは、ウィンドウを表示してユーザがウィンドウを操作 することによって実行されますが、バッチファイルはその名のとおりバッチ処理 を行います。
このため、ウィンドウを開くアプリケーションと同期して動作することが難しく、 使い道を限らせてしまうようです。
そこで、バッチファイル用の外部コマンド (コンソールアプリケーション)を作成して、ウィンドウを持つアプリケーションを バッチファイルで操作できるようにしたいのですが、一つだけ困ったことが あります。
それは、Windows95 のバッチファイルは、COMMAND.COM (16ビット アプリケーション)で実行されるということです。
Cのソースファイルをコンパイルするときに利用する、NMAKE.EXECL.EXE などの32ビットコンソールアプリケーションは、コンソールに フォーカスがなくても動作します。
しかし、バッチファイルを実行しているコンソールにフォーカスがないとき には、バッチファイルの処理は停止してしまいます。
このため、Windows95 で他のウィンドウを制御するときは、バッチファイルを実行した コンソールからフォーカスが移動しないときだけ制御を行うことができます。
なお、Windows95 では32ビット環境でのバッチファイルはサポートしていませんので、 WindowsNTでなけれな動作しないバッチファイルは、拡張子をCMDにしておくと よいでしょう(注1)。
(注1)もともと、CMD はOS/2のバッチファイル拡張子ですがWindowsNTでは、 この拡張子をもつバッチファイルをサポートしています。

○バッチファイルからウィンドウを操作するための外部コマンドを作成
バッチファイルからウィンドウを操作するためのコマンドは標準ではサポート されません。そこで、操作を行うための外部コマンドを作成します。
  1. バッチファイルを実行しているコンソールを非表示にする
  2. 指定されたウィンドウが存在するかを得る
  3. 指定されたウィンドウにメッセージを送信する
    これらの外部コマンド以外に、
  4. 指定時間処理を停止する
  5. 他のマシンにメッセージを送信する
    といった機能を補助的に追加します。

9-4 コンソールを非表示にする

○コンソールの制御は複雑
コンソールアプリケーションは起動すると、必ずコンソールウィンドウを開きます。 しかし、このウィンドウは意外と邪魔物にされることが多く、できれば消してしまい たいことがあります。自動的に表示されるコンソールを消すには、 Win32 API FreeConsole() を利用することができます。
しかし、このAPIを利用してコンソールを削除すると、標準入出力 (stdin, stdout, stderr) とコンソールとのリンクがなくなり、利用できなく なります。
これらのハンドルは、Win32 API CreateConsole でコンソールを再度生成 しても元のハンドルとは異なるため、それぞれのハンドルを再度設定し直すまで利用 できません。
そこで、コンソールウィンドウを通常のウィンドウと同様に制御する方法を 考えます。

○一時的にユニークな名前をつけてウィンドウハンドルを取得
コンソールは、他のウィンドウとは異なるAPIで生成しますが、ウィンドウ ハンドルを取得することができれば、他のウィンドウと同様に制御することが できます。
しかし、コンソールのウィンドウクラス名は、変化しないため、コンソールと他の ウィンドウを識別することができますが、複数のコンソールを識別することが できません。
このため、ウィンドウ名を利用します。しかし、コンソールのウィンドウ名も実行した ファイル名が表示されたり、PIFファイルで設定した値が利用されるため、 複数のコンソールウィンドウから特定のコンソールウィンドウを識別することが できません。
そこで、コンソールのウィンドウ名は変更するためのAPIである、 Win32 API SetConsoleTitle() 取得するための Win32 API GetConsoleTitle() を利用して一時的にウィンドウ名を 変更します。
つまり、一時的にユニークな名前をつけてウィンドウハンドルを取得し、 ウィンドウハンドルを取得後にコンソールのタイトルを元に戻します。
ここでは、ユニークなウィンドウ名として、アプリケーション名と、システムの起動 からの経過時間を利用していますが、完全に識別するにはも少しユニークな名前である 必要があるかも知れません。 なお、Win32 API SetConsoleTitle() は、ウィンドウ名が完全に変更される前 に関数が終了してしまうようなので、完全に変更されるまで待つ処理が必要 となります(例9-1, 例9-2)。



例9-1:コンソールのウィンドウハンドルを取得する
GetConsoleTitle(szOldText, sizeof(szOldText)); sprintf(szId, "bHide%08X", timeGetTime()); SetConsoleTitle(szId); /* ウィンドウ名が変更されるまで待つ */ do{ Sleep(100); GetConsoleTitle(szTemp, sizeof(szTemp)); }while(strcmp(szId, szTemp) != 0); hWnd = FindWindow(NULL, szId); SetConsoleTitle(szOldText);
例9-2:コンソールを5秒間非表示状態にする
@echo off bhide on bsleep 5 bhide off


9-5 指定されたウィンドウが存在するかを得る

○ウィンドウの状態チェック
バッチファイルからウィンドウを持つアプリケーションをバッチファイルから起動 し、起動したアプリケーションと同期をとって動作するには、そのアプリケーション が表示するウィンドウがどのような状態になっているかを知る必要があります。
たとえば、あるアプリケーションで処理を行い、その結果を元に次の処理を行う必要 があるときや、起動したウィンドウにメッセージを送信してバッチファイルから 操作を行う必要があるとき、ウィンドウがメッセージを受信できる状態になるまで 待つ必要があります。ウィンドウのチェックを行った結果は、main 関数の 戻り値に設定することにより、バッチファイルの if errorlevel で取得する ことができます(例9-3)。

例9-3:指定ウィンドウが生成されるまで待つ

@echo off set WM_COMMAND=273 start winfile.exe ← ファイルマネージャを起動する :loop bsleep 1 ← 1秒待つ bfindwin -cls WFS_Frame ← ファイルマネージャ if errorlevel 1 goto loop ← エラーなら繰り返す



○Windows95の場合:起動時に最小化
さて、Windows95では、ファイルマネージャ自身をアイコン化して起動すると、 フォーマットダイアログボックスだけを表示させることができます。
ファイルマネージャの起動をチェックするときと同様に、ダイアログボックスの存在を チェックすることで、フォーマットが終了したかを検出することも可能に なります。

○Windows95の場合:PIFファイル設定
Windows95 では、16ビット、32ビットアプリケーション共にPIFファイルの設定に よってコンソール(DOSプロンプト)の動作が決定されます。
ここで注意しなければならないのは、PIFファイルの
[その他]タグ - 「バックグラウンド時の設定」
が、
「つねに実行を中断」
になっている場合、コンソールウィンドウのフォーカスを奪われないように 他のウィンドウにメッセージを送信しなければ、送信元、送信先ともにデッドロックを 起こすことがあります。

○Windows95のコンソール表示プログラムCONAGENT.EXE
コンソールアプリケーションを実行するとき、システムは
C:\WINDOWS\SYSTEM\CONAGENT.EXE
を起動してコンソールの表示を行います。CONAGENT.EXE は、16ビット アプリケーションで、32ビットアプリケーションが起動されるとCONAGENT.EXE が起動されます。
起動したアプリケーションからコンソールへのアクセスが行われると、 CONAGENT.EXE によって、VCOND(仮想コンソールデバイス)の呼び出しが 行われます。これは、コンソールの内部制御が16ビットコードで行われているため です。
CONAGENT.EXE の起動時に、
C:\WINDOW\S\SYSTEM\CONAGENT.PIF
が存在すると、その設定に従い、環境設定が行われます。このPIFファイルの設定が、 バックグラウンド時の設定が「常に実行を中断」になっていると、 コンソールからフォーカスが奪われると処理が停止されます。

○Windows95のCONAGENT.PIFの設定
コンソールを Win32 API AllocConsole() でコンソールを生成し、 そのウィンドウにフォーカスが移動すると、マウスカーソルが消えてしまうことが あります。これは、CONAGENT.PIF の設定のうち、
[その他]タグ -「マウスポインタを表示しない(排他モード)
がチェックされているために起こります。この設定は、マウスの制御権を占有しなけれ ば正しく動作しないMS-DOSアプリケーションのために利用します。
この設定はコンソールアプリケーションには無意味なうえトラブルの素になります ので、CONAGENT.PIF ではこの設定を行わないほうがよいでしょう。

9-6 指定されたウィンドウにメッセージを送信する

ウィンドウを持つアプリケーションに、キーやマウスで操作しているときと同じ メッセージを送信することで、簡単に制御することができます(例9-4)。
ウィンドウにメッセージを送信するには、送信先ウィンドウのウィンドウハンドルが 必要になります。ウィンドウハンドルは、ウィンドウの存在を調べるときと同様に、 ウィンドウ名やクラス名が特定できれば、Win32 API FindWindow() で取得 することができます。取得したウィンドウハンドルを利用して、 Win32 API PostMessage() でウィンドウにメッセージを送信します。

例9-4:指定ウィンドウを閉じる

@echo off set WM_CLOSE=16 bmessage -win %1 -cls %2 %WM_CLOSE% 0 0 set WM_CLOSE=



9-7 補助用外部コマンド(処理を一時停止する)

ウィンドウの存在をチェックするときなど、処理を一時的に停止してある一定時間 処理を停止して、ある一定時間後に再度処理を行うといった処理を行う必要が あります。
もし、処理を単純にループすると、無限ループ状態になり、CPUがその処理に集中 して、他の処理に影響を与えるためです。
また、失敗した処理が次の瞬間に成功することは少ないため、一定時間待って再試行 するようにします。
処理を中断して、一定時間CPUをシステムに譲るには、Win32 API Sleep() で停止 する時間を指定します。

9-8 補助用外部コマンド(他のマシンにメッセージを送信する)

コンソールウィンドウを非表示にして実行させて状態がわからなくなったり、 サーバ上でバッチファイルを実行して、終了するまで、他のマシンで作業をしたい とき、バッチファイルから処理が終了したことを知らせる必要があります。
他のマシンにメッセージを送信したいとき、NET.EXE を利用することが できます。NET コマンドはMS-DOSのLANマネージャから利用できるため、複数のOSで 稼動するマシンで構成されるLAN環境では効果的に利用することができます。
NETコマンドで送信したメッセージは、OS/2, Windows3.1, Windows95, WindowsNT で受信することができますが、Windows3.1 や Windows95 では、ポップアップ サービス (WINPOPUP.EXE) をあらかじめ起動しておく必要があります。
NETコマンドでのメッセージ送信は、
NET SEND /?
で利用方法を知ることができます。
ただし、Windows95 では NET SEND コマンドはサポートしていません。

○メールスロット
NETコマンドや ポップアップサービスはメールスロットAPIを利用しています (表9-2, 表9-3)。
Windows95 や WindowsNTのメールスロットAPIは、OS/2のLANマネージャで提供される 機能のサブセット版で、MS-DOSのLANマネージャとも互換性があります。
メールスロットAPIは、データグラム型の通信を行い、1対1または、1対多の通信を 行うことができます。
クライアントは、任意のマシンのサーバにメッセージを送信することができます。 ただし、メッセージサイズは最大400バイトに制限されます。
LANマネージャのメールスロットには、クラス1とクラス2のメッセージ機能が ありますが、Windows95やWindowsNT ではクラス2のメッセージ機能をサポートして います。
TCP/IPのTCPや名前付きパイプは1対1のセッションが開設されると、 他のクライアントは割り込むことはありません(図9-2)。
このため、サーバで受け取るデータは必ず特定クライアントのデータであることが 保証されます。しかし、ソケットのUDPやメールスロットは、複数のクライアントから 同時にメッセージを受け取ってしまいます。このため、メールスロットサーバは、 受け取ったデータが誰のものかを識別して処理を行う必要があります。データグラム型 の通信は、テレビやラジオのようにサーバがデータを正しく受信したかをチェック しませんので、確実にデータを送信したいときには、名前付きパイプを利用したほうが よいでしょう。

○メールスロットのパス名
Microsoft Win32 SDK KnowledgeBase
BUG: Windows95 Limits Mailslot Names to 8.3 Naming Convertion(PSS ID:Q139716)
で、Windows95 で利用するのメールスロットのパス名は、8.3形式でなければ ならないとありますが、筆者のマシンでは、8.3を超えるファイル名でも正常に動作 します。日本語版は、US版のバグが修正されていることもありますし、 また Service Pack を入手できますので、どのバージョンなら問題がないかを確認して おく必要があるかもしれません。
また、WindowsNTがメールスロットサーバで、Windows95がクライアントとして送信する ときも、この制限を受けるようです。このため、WindowsNTで動作するときでも、 クライアントにWindows95を利用するときは、8.3形式である必要があります。

表9-2:LAN Manager2.x と Win32 API のメールスロット
LAN Manager 2.xのメールスロット関数 Win32 API のメールスロット関数
DosDeleteMailslotCloseHandle
DosMailslotInfoGetMailslotInfo
DosMakeMailslotCreateMailslot
DosReadMailslotReadFile
DosWriteMailslotWriteFile

表9-3:メールスロットで利用できる関数
メールスロットサーバ用関数
CreateMailslot
GetMailslotInfo
SetMailslotInfo
DuplicateHandle
ReadFile
GetFileTime
SetFileTime
メールスロットクライアント用関数
CloseHandle
CreateFile
DuplicateHandle
WriteFile


図9-2:パイプとメールスロットの違い

+--------------+ +--------------+ | | ------------------------------ | | | サーバ | ○○○ ← | クライアント | | | ------------------------------ | | +--------------+ |↑| +--------------+ | | 割り込めない (a)TCP/IPのTCPや名前付きパイプ +--------------+ +-------------------------- | | | ← ○ | クライアント | | +--------------------- | | | | +--------------+ | | +--------------+ | | +--------------+ | | ---+ +--------------------- | | | サーバ | ○△□ ← △ | クライアント | | | ---+ +--------------------- | | +--------------+ | | +--------------+ | | | | +--------------+ | +--------------------- | | | ← □ | クライアント | +-------------------------- | | +--------------+ (b)TCP/IPのUDP,ICMPやメールスロット


9-9 プログラムについて

○バッチファイル用ウィンドウ制御コマンド \BATFILES\CMD
バッチファイルで利用できる、ウィンドウ制御コマンドと、メッセージ送受信 ツールです。
(1)バッチファイルを実行しているコンソールを非表示にするBHIDE.EXE (BHIDE.C)
使い方
>BHIDE ON | OFF
ON ... コンソールを非表示状態にする
OFF ... コンソールを表示状態にする

(2)指定されたウィンドウが存在するかを得るBFINDWIN.EXE(BFINDWIN.C)
使い方
>BFINDWIN [ -cls クラス名 ] [ -win ウィンドウ名 ]
クラス名またはウィンドウ名または両方を指定します。
ERRORLEVEL = 0 ... 指定されたウィンドウは存在します
ERRORLEVEL = 1 ... 指定されたウィンドウは存在しません
ウィンドウの検索は、Win32 API FindWindow() で可能ですが、本コマンド では、Win32 API EnumWindows()EnumChildWindows() で行って います。
ソース内の #if 0#if 1 にすることにより、全てのウィンドウを 列挙することができます。

(3)指定されたウィンドウにメッセージを送信するBMESSAGE.EXE (BMESSAGE.C)
使い方
>BMESSAGE [ -cls クラス名 ] [ -win ウィンドウ名 ] uMsg wParam lParam
クラス名またはウィンドウ名または両方を指定します。
uMsg ... メッセージIDを10進文字列で指定する
wParam ... パラメータを10進文字列で指定する
lParam ... パラメータを10進文字列で指定する
ERRORLEVEL = 0 ... 正常終了
ERRORLEVEL = 1 ... 異常終了

(4)指定時間処理を停止するBSLEEP.EXE(BSLEEP.C)
使い方
>BSLEEP 時間
時間を秒で10進文字列で指定する

(5)他のマシンにメッセージを送信するBSEND.EXE(BSEND.C)
使い方
>BSEND マシン名 メッセージ
RCVMSG.EXEで受信できるメッセージを送信します。

○サンプルバッチファイル \BATFILES\CMD
(1)FMT.BAT
ファイルマネージャを起動してフォーマットダイアログボックスを表示させます。
(2)BHIDE.BAT
5秒間コンソールウィンドウを非表示(HIDE)状態にします。

○メッセージ送受信ツール \BATFILES\RCVMSG
インジケータ領域にアイコンを表示して常駐するメッセージ送受信ツールです。
メッセージを受信すると、メッセージボックスにメッセージを表示させます。
送信は、インジケータ領域のアイコンをダブルクリックするか、ポップアップメニュー から送信を選択すると、メッセージ送信ダイアログボックスを表示します。
RCVMSG.C(本体)
SENDDATA.C(メッセージ送信ダイアログボックス)
RCVMSG.H(ヘッダファイル)
RCVMSG.RC(リソースファイル)
RCVMSG.ICO(アイコンファイル)
MAKEFILE(メイクファイル)
NETコマンドや、ポップアップサービスは標準で提供されるため、これらのツールを 利用してもよいのですが、用途に合わせてカスタマイズすることができません。
そこで、これらの機能に似たものを作成して、環境や用途に合わせてカスタマイズで きれば便利ではないかと思います。 このサンプルは、カスタマイズして利用することを前提としていますので、必要最小限 の機能しか持たせていません。
ポップアップサービスには、メッセージのログ機能がありますが、WindowsNT の メッセンジャーサービスは、受信したメッセージをいきなりデスクトップに表示して しまいます。WindowsNTで ポップアップサービスのような機能を利用したいときには、 自家製の「ポップアップサービス」 を作成する必要があります。