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

Windows APIメモ CopyFileEx, CopyFileExW

Windows APIのメモ。
CopyFileEx, CopyFileExW。

参考サイト

CopyFileEx

function CopyFileEx(
    lpExistingFileName,
    lpNewFileName       : PChar;
    lpProgressRoutine   : TFNProgressRoutine;
    lpData              : Pointer;
    pbCancel            : PBool;
    dwCopyFlags         : DWORD
): BOOL; stdcall;

ファイルコピー中に進捗状況を表示させるためにはCopyFileEx APIを使います。
ちょっと難しそうに思えますが第3引数にコピーの進捗状況を表示するコールバック関数のアドレスを指定するだけなのでそう難しいことはありません。
第4引数でポインタを介してコールバック関数へデータを渡すこともできますがよく分からなければそれにこだわることなくグローバル変数を使ってやり取りすればOKです。
下の例ではグローバル変数のForm1を使ってForm1のキャプションに進捗状況を表示しています。

//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 (CopyFileEx(
          PChar(OpenDialog1.FileName),
          PChar(SaveDialog1.FileName),
          @CopyProgressRoutine,
          nil,
          nil,  //特に必要がなければnilでOK
          0
      )) then begin
        Caption := 'コピーは成功しました';
      end else begin
        Caption := 'コピーは失敗しました';
      end;
    end;
  end;
end;

CopyFileEx関数の第5引数には特に必要がなければnilを指定してもかまわないようです。
必要な場合はグローバル変数かフォームのプライベート変数を指定すると良いでしょう。

var
  bCancel: Boolean;

procedure TForm1.Button1Click(Sender: TObject);
begin
  //コピー元ファイルを選択
  if (OpenDialog1.Execute) then begin
    //コピー先ファイルを指定
    SaveDialog1.FileName := OpenDialog1.FileName;
    if (SaveDialog1.Execute) then begin
      bCancel := False;
      if (CopyFileEx(
          PChar(OpenDialog1.FileName),
          PChar(SaveDialog1.FileName),
          @CopyProgressRoutine,
          nil,
          @bCancel,
          0
      )) then begin
        Caption := 'コピーは成功しました';
      end else begin
        Caption := 'コピーは失敗しました';
      end;
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
//コピー中止
begin
  bCancel := True;
end;

Form1のprivate変数でもOKです。

  private
    { Private 宣言 }
    F_bCancel: Boolean;

  ...


procedure TForm1.Button1Click(Sender: TObject);
begin
  //コピー元ファイルを選択
  if (OpenDialog1.Execute) then begin
    //コピー先ファイルを指定
    SaveDialog1.FileName := OpenDialog1.FileName;
    if (SaveDialog1.Execute) then begin
      F_bCancel := False;
      if (CopyFileEx(
          PChar(OpenDialog1.FileName),
          PChar(SaveDialog1.FileName),
          @CopyProgressRoutine,
          nil,
          @F_bCancel,
          0
      )) then begin
        Caption := 'コピーは成功しました';
      end else begin
        Caption := 'コピーは失敗しました';
      end;
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
//コピー中止
begin
  F_bCancel := True;
end;

CopyFileExW

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

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 (CopyFileExW(
          PWideChar(WideString(OpenDialog1.FileName)),
          PWideChar(WideString(SaveDialog1.FileName)),
          @CopyProgressRoutine,
          nil,
          nil,  //特に必要がなければnilでもOK
          0
      )) then begin
        Caption := 'コピーは成功しました';
      end else begin
        Caption := 'コピーは失敗しました';
      end;
    end;
  end;
end;

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