2015-08-27 35 views
5

GUI InnoSetup của tôi bị đóng băng trong quá trình giải nén.Làm cách nào để thực thi 7zip mà không chặn giao diện người dùng InnoSetup?

Tôi đã một procedure DoUnzip(source: String; targetdir: String) với cốt lõi

unzipTool := ExpandConstant('{tmp}\7za.exe'); 

Exec(unzipTool, ' x "' + source + '" -o"' + targetdir + '" -y', 
    '', SW_HIDE, ewWaitUntilTerminated, ReturnCode); 

Thủ tục này được gọi là nhiều lần và các khối hoạt động Exec giao diện người dùng. Chỉ có một khoảnh khắc rất ngắn giữa các lần thực thi, nơi mà giao diện đồ họa Inno có thể kéo/di chuyển được.

Tôi biết rằng có các tùy chọn khác cho TExecWait thay vì ewWaitUntilTerminated, như ewNoWaitewWaitUntilIdle, nhưng tiếc là chúng không hữu ích trong trường hợp này. Sử dụng ewNoWait sẽ dẫn đến việc thực thi nhiều thao tác giải nén cùng một lúc.

Tôi đang tìm cách thực hiện thao tác giải nén bên ngoài và đợi cho đến khi hoàn tất, nhưng không chặn giao diện người dùng. Làm thế nào tôi có thể thực hiện điều đó?


Dưới đây là ghi chú và ý tưởng của tôi:

Chờ đợi cho một quá trình để kết thúc, được ngăn chặn, trừ khi bạn sẽ được chờ đợi trong một chủ đề khác nhau từ một trong những chính. Tôi nghĩ rằng một số loại gọi lại là cần thiết, được thực hiện, khi các hoạt động giải nén kết thúc.

Tôi biết rằng InnoSetup không cung cấp tính năng này ra khỏi hộp, xem https://github.com/jrsoftware/issrc/issues/149

Trong khi tìm kiếm các vấn đề có liên quan trên StackOverflow, tôi đã đưa ra câu hỏi Using callback to display filenames from external decompression dll (Inno Setup), nơi tôi tìm thấy Mirals's answer. Nó sử dụng InnoCallback kết hợp với một DLL.

Tôi nghĩ, trong trường hợp của tôi, điều này có thể là 7zxa.dll cho hoạt động giải nén. Nhưng nó không chấp nhận gọi lại. Vì vậy, đoạn mã sau chỉ là một bản thảo khái niệm/ý tưởng. Một vấn đề là, 7zxa.dll không chấp nhận gọi lại. Một vấn đề khác là API 7zxa không thực sự mời làm việc.

[Code] 
type 
    TMyCallback = procedure(Filename: PChar); 

// wrapper to tell callback function to InnoCallback 
function WrapMyCallback(Callback: TMyCallback; ParamCount: Integer): LongWord; 
    external '[email protected]:innocallback.dll stdcall'; 

// the call to the unzip dll 
// P!: the 7zxa.dll doesn't accept a callback 
procedure DoUnzipDll(Blah: Integer; Foo: String; ...; Callback: LongWord); 
    external '[email protected]:7zxa.dll stdcall'; 

// the actual callback action 
procedure MyCallback(Filename: PChar); 
begin 
    // refresh the GUI 
end; 

//----- 

var Callback : LongWord; 

// tell innocallback the callback procedure as 1 parameter 
Callback := WrapMyCallback(@MyCallback, 1); 

// pass the wrapped callback to the unzip DLL 
DoUnzipDll(source, target, ..., Callback); 

procedure DoUnzip(src, target : String); 
begin 
    DoUnzipDll(ExpandConstant(src), ExpandConstant(target)); 
end; 

Cập nhật

@Rik gợi ý để kết hợp các chức năng WinAPI ShellExecuteEx() với INFINITE WaitForSingleObject.

Tôi đã triển khai và thử nghiệm phương pháp này. Mã dưới đây.

Công việc giải nén, nhưng cửa sổ InnoSetup chỉ có thể di chuyển/kéo được trong một khoảng thời gian ngắn giữa các thao tác giải nén riêng lẻ. Trong quá trình giải nén dài, GUI sẽ hoàn toàn không phản hồi - không có nút kéo/không hủy. Tôi đã thêm BringToFrontAndRestore(), nhưng có vẻ như quy trình mới có tiêu điểm.

const 
    WAIT_OBJECT_0 = $0; 
    WAIT_TIMEOUT = $00000102; 
    SEE_MASK_NOCLOSEPROCESS = $00000040; 
    INFINITE = $FFFFFFFF;  { Infinite timeout } 

type 
    TShellExecuteInfo = record 
    cbSize: DWORD; 
    fMask: Cardinal; 
    Wnd: HWND; 
    lpVerb: string; 
    lpFile: string; 
    lpParameters: string; 
    lpDirectory: string; 
    nShow: Integer; 
    hInstApp: THandle;  
    lpIDList: DWORD; 
    lpClass: string; 
    hkeyClass: THandle; 
    dwHotKey: DWORD; 
    hMonitor: THandle; 
    hProcess: THandle; 
    end; 

function ShellExecuteEx(var lpExecInfo: TShellExecuteInfo): BOOL; 
    external 'ShellExecuteEx{#AW}@shell32.dll stdcall'; 
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD; 
    external '[email protected] stdcall'; 
function CloseHandle(hObject: THandle): BOOL; external '[email protected] stdcall'; 

procedure DoUnzip(source: String; targetdir: String); 
var 
    unzipTool, unzipParams : String;  // path to unzip util 
    ReturnCode : Integer; // errorcode 
    ExecInfo: TShellExecuteInfo; 
begin 
    // source might contain {tmp} or {app} constant, so expand/resolve it to path name 
    source := ExpandConstant(source); 

    unzipTool := ExpandConstant('{tmp}\7za.exe'); 
    unzipParams := ' x "' + source + '" -o"' + targetdir + '" -y'; 

    ExecInfo.cbSize := SizeOf(ExecInfo); 
    ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS; 
    ExecInfo.Wnd := 0; 
    ExecInfo.lpFile := unzipTool; 
    ExecInfo.lpParameters := unzipParams; 
    ExecInfo.nShow := SW_HIDE; 

    if not FileExists(unzipTool) 
    then MsgBox('UnzipTool not found: ' + unzipTool, mbError, MB_OK) 
    else if not FileExists(source) 
    then MsgBox('File was not found while trying to unzip: ' + source, mbError, MB_OK) 
    else begin 

      // ShellExecuteEx combined with INFINITE WaitForSingleObject 

      if ShellExecuteEx(ExecInfo) then 
      begin 
      while WaitForSingleObject(ExecInfo.hProcess, INFINITE) <> WAIT_OBJECT_0 
      do begin 
       InstallPage.Surface.Update;   
       //BringToFrontAndRestore; 
       WizardForm.Refresh(); 
      end; 
      CloseHandle(ExecInfo.hProcess); 
      end; 

    end; 
end; 
+0

Tôi có thể là cách tắt cơ sở ở đây nhưng có thể bạn không chỉ cần sử dụng 'shellexecuteex' và 'WaitForSingleObject' với thời gian chờ INFINITE? Nếu nó vẫn chặn với thời gian chờ INFINITE, bạn có thể sử dụng thời gian chờ nhỏ hơn và lặp lại cho đến khi quá trình kết thúc. Xem [câu trả lời ở đây] (http://stackoverflow.com/a/10910780/1037511). – Rik

+0

Đó là một ý tưởng thú vị. Cảm ơn bạn! Tôi sẽ kiểm tra cách tiếp cận đó và báo cáo lại. –

+0

@Rik Tôi đã triển khai ý tưởng của bạn và thêm mã cho nó vào câu hỏi của tôi. Cửa sổ sẽ chỉ đáp ứng cho thời gian ngắn giữa nhiều thao tác giải nén. Trong quá trình giải nén dài, giao diện Inno vẫn bị đóng băng. –

Trả lời

6

Giống như tôi nghi ngờ sử dụng INFINITE với WaitForSingleObject vẫn chặn luồng chính. Tiếp theo, tôi nghĩ sử dụng thời gian chờ nhỏ hơn với WaitForSingleObject. Nhưng vấn đề vẫn là chủ đề chính vẫn nằm trong vòng lặp while là WaitForSingleObject và không phản ứng lại việc di chuyển. WizardForm.Refresh không làm cho nó di chuyển.Nó chỉ làm mới biểu mẫu nhưng không xử lý các thư khác (như WM_MOVE). Bạn cần một cái gì đó như Application.ProcessMessages để cho phép các cửa sổ di chuyển. Vì Inno Setup không có ProcessMessages nên chúng ta có thể tự tạo một bản thân.

Dưới đây là mã của bạn với số ProcessMessage được triển khai. Nó chờ đợi 100 mili giây cho WaitForSingleObject và nếu nó vẫn ở trạng thái đợi, nó sẽ thực hiện ProcessMessageRefresh. Điều này sẽ cho phép bạn di chuyển cửa sổ. Bạn có thể chơi một chút với giá trị 100.

Một cách khác có thể là bạn lưu ExecInfo và tiếp tục với một số phần cài đặt khác. Trong trang cuối cùng, bạn có thể kiểm tra xem quá trình đã hoàn tất chưa. Nếu nó không lặp với AppProcessMessage cho đến khi nó được.

[Code] 
#ifdef UNICODE 
    #define AW "W" 
#else 
    #define AW "A" 
#endif 

const 
    WAIT_OBJECT_0 = $0; 
    WAIT_TIMEOUT = $00000102; 
    SEE_MASK_NOCLOSEPROCESS = $00000040; 
    INFINITE = $FFFFFFFF;  { Infinite timeout } 

type 
    TShellExecuteInfo = record 
    cbSize: DWORD; 
    fMask: Cardinal; 
    Wnd: HWND; 
    lpVerb: string; 
    lpFile: string; 
    lpParameters: string; 
    lpDirectory: string; 
    nShow: Integer; 
    hInstApp: THandle;  
    lpIDList: DWORD; 
    lpClass: string; 
    hkeyClass: THandle; 
    dwHotKey: DWORD; 
    hMonitor: THandle; 
    hProcess: THandle; 
    end; 

function ShellExecuteEx(var lpExecInfo: TShellExecuteInfo): BOOL; 
    external 'ShellExecuteEx{#AW}@shell32.dll stdcall'; 
function WaitForSingleObject(hHandle: THandle; dwMilliseconds: DWORD): DWORD; 
    external '[email protected] stdcall'; 
function CloseHandle(hObject: THandle): BOOL; external '[email protected] stdcall'; 

//----------------------- 
//"Generic" code, some old "Application.ProcessMessages"-ish procedure 
//----------------------- 
type 
    TMsg = record 
    hwnd: HWND; 
    message: UINT; 
    wParam: Longint; 
    lParam: Longint; 
    time: DWORD; 
    pt: TPoint; 
    end; 

const 
    PM_REMOVE  = 1; 

function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): BOOL; external '[email protected] stdcall'; 
function TranslateMessage(const lpMsg: TMsg): BOOL; external '[email protected] stdcall'; 
function DispatchMessage(const lpMsg: TMsg): Longint; external '[email protected] stdcall'; 

procedure AppProcessMessage; 
var 
    Msg: TMsg; 
begin 
    while PeekMessage(Msg, WizardForm.Handle, 0, 0, PM_REMOVE) do begin 
    TranslateMessage(Msg); 
    DispatchMessage(Msg); 
    end; 
end; 
//----------------------- 
//----------------------- 


procedure DoUnzip(source: String; targetdir: String); 
var 
    unzipTool, unzipParams : String;  // path to unzip util 
    ReturnCode : Integer; // errorcode 
    ExecInfo: TShellExecuteInfo; 
begin 
    // source might contain {tmp} or {app} constant, so expand/resolve it to path name 
    source := ExpandConstant(source); 

    unzipTool := ExpandConstant('{tmp}\7za.exe'); 
    unzipParams := ' x "' + source + '" -o"' + targetdir + '" -y'; 

    ExecInfo.cbSize := SizeOf(ExecInfo); 
    ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS; 
    ExecInfo.Wnd := 0; 
    ExecInfo.lpFile := unzipTool; 
    ExecInfo.lpParameters := unzipParams; 
    ExecInfo.nShow := SW_HIDE; 

    if not FileExists(unzipTool) 
    then MsgBox('UnzipTool not found: ' + unzipTool, mbError, MB_OK) 
    else if not FileExists(source) 
    then MsgBox('File was not found while trying to unzip: ' + source, mbError, MB_OK) 
    else begin 

      // ShellExecuteEx combined with INFINITE WaitForSingleObject 

      if ShellExecuteEx(ExecInfo) then 
      begin 
      while WaitForSingleObject(ExecInfo.hProcess, 100) = WAIT_TIMEOUT { WAIT_OBJECT_0 } 
      do begin 
       AppProcessMessage; 
       //InstallPage.Surface.Update;   
       //BringToFrontAndRestore; 
       WizardForm.Refresh(); 
      end; 
      CloseHandle(ExecInfo.hProcess); 
      end; 

    end; 
end; 

(Mã này được kiểm tra và làm việc cho tôi)

+0

:) Các thay đổi của bạn được phát hiện tại: AppProcessMessage với thời gian chờ thấp sẽ thực hiện thủ thuật! Cảm ơn bạn rất nhiều vì ý tưởng và thời gian của bạn! –

Các vấn đề liên quan