1. 程式人生 > >NSIS教程(8): 基於第三方介面庫的安裝包介面

NSIS教程(8): 基於第三方介面庫的安裝包介面

前面的幾篇文章介紹了NSIS的傳統介面的安裝包和現代介面的安裝包的製作方法,也提到了NSIS支援自定義頁面(即使用page custom)的特性,自定義頁面需要使用者自己建立對話方塊、控制元件、新增控制元件響應等等,雖然NSIS提供了nsDialogs.nsh來支援這些功能,但使用起來還是不太方便(需要專門瞭解這個外掛諸多用法),而且不夠靈活,所以本文介紹一種終極的自定義介面的安裝包解決方案,即完全使用第三方介面庫來繪製安裝包介面。

該方案是對介面庫沒有限制的,可以使用其他任何介面庫,如MFC, Qt,WTL等。通過這種方案可以很輕鬆的實現類似金山毒霸、QQ、360安全衛士等軟體的安裝包介面。

一、原理

page custom [建立函式] [離開函式] [標題]

使用第三方介面庫完全定製安裝包介面的基本原理就是:新建一個dll外掛,在page custom[建立函式]中呼叫該外掛中的函式來顯示介面,這時介面上面的按鈕的響應就不再由NSIS控制了,完全由我們的程式碼控制。

二、難點問題

使用我們的外掛dll完全替代NSIS介面之後,有幾個問題需要解決:
1. 安裝\解除安裝進度
2. C++回撥NSIS函式

2.1 安裝\解除安裝進度

NSIS中的安裝\解除安裝進度由!insertmacro MUI_PAGE_INSTFILESPage instfiles

提供,在完全使用自己的介面之後,這2個NSIS介面都不能使用了,這時我們需要自己獲取安裝(釋放)\解除安裝(刪除)的進度。以安裝進度為例,NSIS中檔案的安裝時檔案釋放功能都是由File命令提供,但該命令沒有提供釋放進度,所以我們無法獲取到實時的釋放進度。在這裡我們可以使用一個曲折的方法,我們將一個7z壓縮包放入安裝包中,

SetOutPath $INSTDIR
File "app\app.7z"

等安裝包釋放完這個壓縮包之後(這段時間的進度無法顯示),再使用NSIS官方提供的nsis7z外掛來解壓縮這個7z壓縮包,由於nsis7z外掛可以提供解壓縮排度,所以我們可以將這個進度顯示在安裝進度頁面上,解壓完之後再刪除7z壓縮包。這個方案的一個弊端就是,7z壓縮包從安裝包中釋放到本地磁碟的過程需要時間,且這個時間無法準確的顯示在進度頁面。

Function ExtractFunc
    SetOutPath $INSTDIR
    File "app\app.7z"
    GetFunctionAddress $R9 ExtractCallback
    Nsis7z::ExtractWithCallback "$INSTDIR\app.7z" $R9
    Delete "$INSTDIR\app.7z"
FunctionEnd
Function ExtractCallback
    Pop $1
    Pop $2
    System::Int64Op $1 * 100
    Pop $3
    System::Int64Op $3 / $2
    Pop $0

    nsDui::SetSliderValue "slrProgress" $0

    ${If} $1 == $2
        nsDui::SetSliderValue "slrProgress" 100
        nsDui::NextPage "wizardTab"
    ${EndIf}
FunctionEnd

寫這篇文字的時候,發現現在的nsis7z已經太老了,新版的壓縮軟體生成的7z壓縮包,該外掛已經無法解壓。可以使用7za.exe命令列工具來生成7z壓縮檔案,7za.exe從此處下載:http://download.csdn.net/download/china_jeffery/10214464
7za生成7z壓縮包語法為:
7za.exe a app.7z app\*

2.2 C++回撥NSIS函式

比如使用者點選了我們自定義介面上的“取消”按鈕,這時我們需要呼叫NSIS的Abort函式來取消安裝。那麼就需要解決如何從C++環境回撥到NSIS環境? 我們同樣可以使用NSIS教程(7): 開發第三方外掛中介紹的plugin-common.h來實現該功能。
大致原理是,在NSIS指令碼中初始化自定義介面的控制元件與NSIS函式指標(整型)的繫結關係(如控制元件名–函式名),使用者點選控制元件之後,查詢到該控制元件繫結的NSIS函式,然後呼叫extra_parameters::ExecuteCodeSegment函式(函式第一個引數就是NSIS函式指標)。

以duilib介面庫為例,對NSIS暴露OnControlBindNSISScript介面,提供繫結控制元件與NSIS函式指標(整型)的功能:

NSISAPI  OnControlBindNSISScript(HWND hwndParent, int string_size, char *variables, stack_t **stacktop, extra_parameters *extra)
{
    char controlName[MAX_PATH];
    ZeroMemory(controlName, MAX_PATH);

    popstring(controlName);
    int callbackID = popint();
    g_pMainDlg->SaveToControlCallbackMap( controlName, callbackID );
}

在NSIS中呼叫OnControlBindNSISScript繫結控制元件與NSIS函式:

GetFunctionAddress $0 OnExitDUISetup
nsDui::OnControlBindNSISScript "btnFinishedClose" $0

在duilib的Notify按鈕事件響應函式中呼叫ExecuteCodeSegment執行NSIS函式:

void CDlgMain::Notify( TNotifyUI& msg )
{
    std::map<CDuiString, int >::iterator iter = m_controlCallbackMap.find( msg.pSender->GetName() );
    if( _tcsicmp( msg.sType, _T("click") ) == 0 ){
        if( iter != m_controlCallbackMap.end() )
            g_pluginParms->ExecuteCodeSegment( iter->second - 1, 0 );
    }
    else if( _tcsicmp( msg.sType, _T("textchanged") ) == 0 ){
        if( iter != m_controlCallbackMap.end() )
            g_pluginParms->ExecuteCodeSegment( iter->second - 1, 0 );
    } else {
        WindowImplBase::Notify(msg);
    }
}