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_INSTFILES
或Page instfiles
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);
}
}