從技術上講,您所顯示的代碼很好,並且可以按預期工作。
但是,它有一個小錯誤。您正在將錯誤的指針值傳遞給參數CopyFileEx()
的pbCancel
。但是,您的代碼不會崩潰,因爲您傳遞的指針實際上被設置爲nil
,並且pbCancel
將接受nil
指針,因此CopyFileEx()
將忽略該參數。
你是什麼應該做的是通過一個BOOL
變量,你可以在任何時間爲TRUE
取消複製的地址。 CopyFileEx()
將爲您監控該變量,當變量設置時,您不需要手動返回PROGRESS_CANCEL
回調函數(如果回調函數遇到與複製本身無關的錯誤,則返回PROGRESS_CANCEL
,並且您想中止該複製作爲結果的錯誤)。不過,我不會使用全局變量。我將使用對執行副本的表單本地的變量。
嘗試一些更喜歡這個:
type
TFormMain = class(TForm)
...
private
CancelCopy: BOOL; // <-- BOOL, not Boolean
...
end;
...
type
TCopyEx = record
Source: String;
Dest: String;
Handle: HWND;
PCancelCopy: PBOOL;
end;
PCopyEx = ^TCopyEx;
const
CFEX_CANCEL = WM_USER + 1;
function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize,
StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD;
hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall;
begin
// no need to watch CancelCopy here...
// do normal status handling here as needed...
// use PCopyEx(lpData)^ as needed...
end;
function CopyExThread(p: PCopyEx): Integer;
begin
try
if not CopyFileEx(PChar(p.Source), PChar(p.Dest), @CopyFileProgress, p, p.PCancelCopy, COPY_FILE_NO_BUFFERING) then
begin
if GetLastError() = ERROR_REQUEST_ABORTED then
SendMessage(p.Handle, CFEX_CANCEL, 0, 0);
end;
finally
Dispose(p);
end;
Result := 0;
end;
procedure TFormMain.ButtonCopyClick(Sender: TObject);
var
Params: PCopyEx;
ThreadID: Cardinal;
begin
New(Params);
Params.Source := EditOriginal.Text;
Params.Dest := EditCopied.Text;
Params.Handle := Handle;
Params.PCancelCopy := @CancelCopy; // <-- pass address of CancelCopy here...
CancelCopy := FALSE;
CloseHandle(BeginThread(nil, 0, @CopyExThread, Params, 0, ThreadID));
end;
procedure TFormMain.ButtonCancelClick(Sender: TObject);
begin
CancelCopy := TRUE;
end;
雖這麼說,別的東西需要注意的 - 你是從TForm.Handle
屬性傳遞HWND
到線程。如果TForm
在線程仍在運行時出於任何原因破壞/重新創建其HWND
,則TCopyEx.Handle
值將指向無效窗口(或更糟糕的是,指向重新使用舊窗口的新窗口HWND
值)。
一般來說,TWinControl.Handle
屬性不是線程安全的,所以單是這一理由,它不是一個TWinControl
對象的HWND
傳遞給工作線程是一個好主意,除非你能保證的HWND
在線程運行時不會被銷燬(在這個例子中,這是不能保證的)。
在本例中,我會用這是保證一個不同HWND
成爲螺紋的壽命,如TApplication.Handle
窗口(發送到這個窗口的消息可以經由TApplication.HookMainWindow()
處理),或調用的結果持久AllocateHWnd()
。
例如:
type
TFormMain = class(TForm)
procedure FormDestroy(Sender: TObject);
...
private
CancelCopy: BOOL; // <-- BOOL, not Boolean
CopyFileExWnd: HWND;
procedure CopyFileExWndProc(var Message: TMessage);
...
end;
...
type
TCopyEx = record
Source: String;
Dest: String;
Handle: HWND;
PCancelCopy: PBOOL;
end;
PCopyEx = ^TCopyEx;
const
CFEX_CANCEL = WM_USER + 1;
function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize,
StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD;
hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall;
begin
...
end;
function CopyExThread(p: PCopyEx): Integer;
begin
try
if not CopyFileEx(
PChar(p.Source), PChar(p.Dest), @CopyFileProgress, p, p.PCancelCopy, COPY_FILE_NO_BUFFERING) then
begin
if GetLastError() = ERROR_REQUEST_ABORTED then
SendMessage(p.Handle, CFEX_CANCEL, 0, 0);
end;
finally
Dispose(p);
end;
Result := 0;
end;
procedure TFormMain.FormDestroy(Sender: TObject);
begin
if CopyFileExWnd <> 0 then
DeallocateHWnd(CopyFileExWnd);
end;
procedure TFormMain.ButtonCopyClick(Sender: TObject);
var
Params: PCopyEx;
ThreadID: Cardinal;
begin
if CopyFileExWnd = 0 then
CopyFileExWnd := AllocateHWnd(CopyFileExWndProc);
New(Params);
Params.Source := EditOriginal.Text;
Params.Dest := EditCopied.Text;
Params.Handle := CopyFileExWnd;
Params.PCancelCopy := @CancelCopy;
CancelCopy := FALSE;
CloseHandle(BeginThread(nil, 0, @CopyExThread, Params, 0, ThreadID));
end;
procedure TFormMain.ButtonCancelClick(Sender: TObject);
begin
CancelCopy := TRUE;
end;
procedure TFormMain.CopyFileExWndProc(var Message: TMessage);
begin
case Message.Msg of
CFEX_CANCEL: begin
...
end;
...
else
Message.Result := DefWindowProc(CopyFileExWnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;
它的罰款。數據競賽有潛力,但它是良性的。 –
因爲線程只讀? –
你說「線程」,但有兩個線程在玩。雖然你可能知道一個是主要的,一個是工作者,但系統只會看到兩個線程。一個線程讀取該變量,另一個線程寫入該變量。這是一場數據競賽。然而,比賽是良性的。 –