お世話になっております。
タイトルに書きましたように「ファイルの上書き」について質問させてください。
まず状況をご説明しますと、AとBの2つのアプリがあります。
AはFormCreateにメインルーチンを記述し、プロジェクトファイルの最下部にて、Application.Runを削除しましたので、起動後にFormCreateを処理し終わったら、そのまま終了するアプリケーションとなっております。
AのFormCreateの最後でBをWinExecで起動しますので、A→Bへバトンを渡して終了するアプリになっております。
ここまでの動作は問題なく実現しております。
BのFormCreateでネット上に最新バージョンのAがアップされていればダウンロードしてきて、AのEXEファイルを上書き更新したいのですが、実際ファイルをダウンロードしてきて上書き出来るまでに約20秒も掛かってしまいます。処理順を箇条書きすると以下の通りです。
(1)起動直後に10秒スリープ
(2)最新バージョン有無のチェック
(3)2でAの最新バージョンがあればテンポラリにダウンロード
(4)テンポラリから実際のディレクトリにファイル移動
このような流れになっております。暫定版ですが4の部分のソースが以下のような感じで、
mvFlg := False;
while ( not mvFlg ) do
begin
// ファイル移動
// mvFlg := MoveFileEx( PChar('c:\temp\a.exe'), PChar('c:\a.exe'),
// (MOVEFILE_REPLACE_EXISTING or MOVEFILE_COPY_ALLOWED) );
mvFlg := MoveFile(PChar('c:\temp\a.exe'), PChar('c:\a.exe'));
if ( not mvFlg ) then
begin
LOG := LOG + DateTimeToStr(Now) + ' - ' + 'a.exe の移動に失敗しました->リトライします.' + #13#10;
end else begin
LOG := LOG + DateTimeToStr(Now) + ' - ' + 'a.exe の移動に成功しました.' + #13#10;
end;
end;
ShowMessage(LOG);
デバッグのため、MoveFileの戻り値を評価してログを書き出して見ると、初回から成功するまでに(私の開発環境の場合)20秒程度も掛かってしまいます。20秒程度経過すると「成功しました」のログをはき出しつつループを抜けているようです。
コメントアウトしてますが MoveFileEx でも試してみましたが、同じく20秒掛かります。
最悪、数十秒待つことで処理を実行できるならば、それで行こうと思いますが、上記のコードは一歩間違うと無限ループに陥りそうで確信が持てません。
上記のような処理をしたい場合、どのようにコードを組み立てたらよろしいでしょうか?ご教授くださると幸いです。
開発環境はDelphi6entでOSはWindowsXP PROです。
解決かどうか分からないのですが、20秒待機という状態は自力で脱しました。
>(1)起動直後に10秒スリープ
この処理のときにPostQuitMessage(0)とすることで、a.exeの上書きは1回で出来るようになりました。
Aのハンドルを取得してWM_CLOSEやWM_DESTROYを送信してもダメだったのに、なぜPostQuitMessage(0)で終了するのか?
解決はしたものの、これでいいかどうかの確信が持てません・・・。
APIを解説しているサイトで調べているんですが、ほとんどが自分を確実に終了させる目的で使われているようです。
しかし今回は、Aを確実に終了させるために、Bの冒頭でPostQuitMessage(0)することで、なぜかAを確実に上書きできるようになりました。
とりあえず解決マークはつけずに自分でも調べてみますが、何かご存じの方がいらっしゃれば、PostQuitMessageについてご教授頂けると幸いです。
通常FormCreateの中で上記のようなループを繰り返すのは良くありません。
FormCreateはまだアプリケーションの起動途中であり初期化がすべて行われた後ではありません。
上記処理はアプリケーションの初期化がすべて終わった後に実行すべきだと思います。
これはApplication.OnIdle等を利用して検出することも出来ます。
私の場合はこういう遅延実行はFormCreateの中で自分にWM_APP+xの適当なメッセージをポストしておいて、そのメッセージハンドラの中で処理するようにしています。
> FormCreateを処理し終わったら、そのまま終了するアプリケーションとなっております。
こんなのは GUI アプリにするのが間違いで、コンソールアプリにするとよいのでは?
もちろん、コンソールを表示する必要はありません。
質問者の方がお休みのようなので補足しておきます。
WinExecは以前との互換性のためにあるもので本来はCreateProcessを使用すべきですがこの件は置いといて・・・
WinExecは呼び出されたプロセスが初期化が完了した時又はタイムアウトになった時に制御が返ってきます。
初期化の完了はプロセスがメッセージループに入った時です(GetMeesage関数を呼び出した時)
今回の件の場合AがWinExecでBを呼び出しているのにBが初期化処理を完了せずにFormCreateでループしているため約30秒のタイムアウトになるまでAはWinExecから制御が戻りません。すらわちAがまだ終了していないのでBはAを上書きすることが出来ません。(デッドロック状態になっている)
PostQuitMessageを出すとOKとのことですがWM_QUITは特別な処理をされるため、おそらくPostQuitMessageを呼んだ時点でWinExecは待ちループを抜けるものと思われます。
したがってBはFormCreateの中では初期化だけをしておいてメッセージループが始まってから件の処理を行えばよいと思います。
またAもFormCreateの中でWinExecを呼び出しているようですがFormCreateでは初期化以外はしないようにしたほうが良いと思います。
ついでに遅延初期化のサンプルをあげておきます
・・・
interface
・・・
const
WM_DELAYEDSTART = WM_APP+1; // これは適当な値
type
TForm1 = class(TForm)
protected
procedure WmDelayedStart(var Msg:TMessage); message WM_DELAYEDSTART;
public
end;
・・・
implementation
・・・
procedure TForm1.FormCreate(Sender: TObject);
begin
// メッセージループに入った時に処理を呼び出すため
PostMessage(Handle, WM_DELAYEDSTART, 0, 0);
end;
procedure TForm1.WmDelayedStart(var Msg: TMessage);
begin
// この時点ですべての初期化が終わっている
// アプリ起動後最初に処理することをここに書く
end;
・・・
Application.OnIdleを利用しても同等のことが行えます
だとすると遅延処理をしてもデッドロックそのものは解決しないのでは?
あっ、間違いました。遅延処理は B のほうでやるわけですね? それならOKですね。
リトライが3回程度失敗したら、終了するようにするのはどうでしょうか。
inc(cnt)
if cnt>=3 then
begin
MessageDlg('接続できませんでした',mtInformation,[mbok],0);
Exit;
end;
この手の処理には必要な気がします。
ツイート | ![]() |