ホーム >プログラム >Delphi 6 ローテクTips

Windows APIメモ MoveFileWithProgress MoveFileWithProgressW

Windows APIのメモ。
MoveFileWithProgressとMoveFileWithProgressW。

参考サイト

MoveFileWithProgress

function MoveFileWithProgress(
    lpExistingFileName,
    lpNewFileName:      PChar;
    lpProgressRoutine:  TFNProgressRoutine;
    lpData:             Pointer;
    dwFlags:            DWORD
): BOOL; stdcall;

CopyFileExのファイル移動版とでもいうべきMoveFileWithProgress APIなんですが、これ一筋縄ではいきません。
移動は成功するのですが、コールバック関数が呼ばれず進捗状況が表示されないのです。
CopyFileExと同じコールバック関数を指定すればいいはずだし、うまくいく場合もあるのですがなぜかコールバック関数が呼ばれないことが多発します。
ネットで調べたところ、他のドライブへ移動するケースでないとコールバック関数は呼ばれないようです。
ということでそういう状況で試してみたところ、どうやらその通りなようです。
第5引数にMOVEFILE_COPY_ALLOWEDを指定して他のドライブへ移動させるとコールバック関数は呼ばれます。
同じドライブでの移動やリネームの場合はコールバック関数は呼ばれませんでした。

//MoveFileWithProgress, CopyFileExから呼ばれるコールバックルーチン
function CopyProgressRoutine(
  iTotalFileSize:          int64;
  iTotalBytesTransferred:  int64;
  iStreamSize:             int64;
  iStreamBytesTransferred: int64;
  dwStreamNumber:          DWORD;
  dwCallbackReason:        DWORD;
  hSourceFile:             THandle;
  hDestinationFile:        THandle;
  lpData:                  Pointer
): DWORD; stdcall;
begin
  if (iTotalFileSize > 0) then begin
    //0で割るエラーを回避
    Form1.Caption := Format('移動しています %d%%', [Trunc(iTotalBytesTransferred / iTotalFileSize * 100)]);
  end else begin
    Form1.Caption := '移動しています';
  end;
  Application.ProcessMessages;
  Result := PROGRESS_CONTINUE;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  //コピー元ファイルを選択
  if (OpenDialog1.Execute) then begin
    //コピー先ファイルを指定
    SaveDialog1.FileName := OpenDialog1.FileName;
    if (SaveDialog1.Execute) then begin
      if (MoveFileWithProgress(
          PChar(OpenDialog1.FileName),
          PChar(SaveDialog1.FileName),
          @CopyProgressRoutine,
          nil,
          MOVEFILE_COPY_ALLOWED //他のドライブへ移動を許可
      )) then begin
        Caption := '移動は成功しました';
      end else begin
        Caption := '移動は失敗しました';
      end;
    end;
  end;
end;

同じドライブの移動やリネームであれば時間はかからないのでわざわざ進捗状況を表示させる必要はない、という判断なのでしょうか。
それならそれでそういう仕様だと納得もいきますが、マイクロソフトには一言そう書いておいて欲しいものだなぁ、、と。
成功なら最低でも一度はコールバック関数が呼ばれるものと期待して表示のための関数を書いていたりすると期待を裏切られて悩んでしまいます。
私は悩みました。

MoveFileWithProgressW

Unicode対応版のMoveFileWithProgressWも第1引数と第2引数の型がPWideCharになっているだけでMoveFileWithProgressと使い方は一緒です。

WideString型の変数ならPWideCharにキャストして渡します。
AnsiString型の変数の場合ならまずWideStringでキャストしてからPWideCharにキャストする点にだけ気をつければOKです。

//MoveFileWithProgress, CopyFileExから呼ばれるコールバックルーチン
function CopyProgressRoutine(
  iTotalFileSize:          int64;
  iTotalBytesTransferred:  int64;
  iStreamSize:             int64;
  iStreamBytesTransferred: int64;
  dwStreamNumber:          DWORD;
  dwCallbackReason:        DWORD;
  hSourceFile:             THandle;
  hDestinationFile:        THandle;
  lpData:                  Pointer
): DWORD; stdcall;
begin
  if (iTotalFileSize > 0) then begin
    //0で割るエラーを回避
    Form1.Caption := Format('移動しています %d%%', [Trunc(iTotalBytesTransferred / iTotalFileSize * 100)]);
  end else begin
    Form1.Caption := '移動しています';
  end;
  Application.ProcessMessages;
  Result := PROGRESS_CONTINUE;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  //コピー元ファイルを選択
  if (OpenDialog1.Execute) then begin
    //コピー先ファイルを指定
    SaveDialog1.FileName := OpenDialog1.FileName;
    if (SaveDialog1.Execute) then begin
      if (MoveFileWithProgressW(
          PWideChar(WideString(OpenDialog1.FileName)),
          PWideChar(WideString(SaveDialog1.FileName)),
          @CopyProgressRoutine,
          nil,
          MOVEFILE_COPY_ALLOWED //他のドライブへ移動を許可
      )) then begin
        Caption := '移動は成功しました';
      end else begin
        Caption := '移動は失敗しました';
      end;
    end;
  end;
end;

この例だとわざわざUnicode対応版のAPIを使う必要もないのですが、まぁ一応例示のためということで。