extern "C"void __declspec(dllexport) __cdecl function(HWND hwndParent, int string_size, TCHAR variables, stack_t stacktop, extra_parameters extra){ EXDLL_INIT();}
NSIS中调用第三方库,所有的接口都是按照这个模板来写的接口参数说明:hwndParent:当前调用接口的窗口句柄(即NSIS生成的安装包安装界面窗口)string_size:用户变量个数variables:用户变量(是不是也可以成为共享变量?),主要是一些不需要声明的变量,但是可以直接赋值,且可以在插件中读取和写入如下所示$0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $R0, $R1, $R2, $R3, $R4, $R5, $R6, $R7, $R8, $R9$INSTDIR,$OUTDIR, $CMDLINE, $LANGUAGEstacktop:参数堆栈,传入的参数就被保存在这个堆栈里面,通过popint/popstring和pushint/pushstring可以进行读取和写入操作extra:传递各种其他类型的变量、堆栈、常量、句柄等,实现回调实现回调:int nFunc = popint();extra->ExecuteCodeSegment( nFunc - 1, hwndParent);
一定要注意 ExecuteCodeSegment 的调用要减1外部调用规则:规则:插件名::插件函数 命令行 $0(共享参数) Param(自定义参数)例如:plugindll::function /NOUNLOAD $0 Param熟悉了基本的流程和调用规则,现在开始对intec插件进行改造主要是添加获取下载进度数据的回调以下是修改intec插件的get方法,实现下载进度的同步和回调:NSIS脚本中intec调用实例:Function DownLoadCallBack ; 0-当前进度(百分比) Pop $0 ; 1-累计大小 Pop $1 ; 2-已下载大小 Pop $2 ; 3-下载速度 ;Pop $3 ; 4-剩余时间 ;Pop $4 ;更新包下载进度 ; 当前进度 push $0 SendMessage $PROGBAR ${PBM_SETPOS} $0 0 ; $PROGBAR:进度条FunctionEnd Function ExtractfuncReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" "CurrentVersion" GetFunctionAddress $R9 DownLoadCallBack inetc::get /SILENT /callback $R9 "https://xxxxx/test_dir/test.7z" "D:/test/test.7z" /end ;新增一个/callback命令,用来传递回调函数,在插件中会新增callback命令的解析 Pop $1 ; 写入值($1="ok"表示下载成功) Push $1 FunctionEnd
以下是插件intec.dll的主要代码,get方法是按照NSIS的接口规则实现的http/https的get请求接口主要新增一个callback命令行参数,实现对回调方法的传递extern "C"void __declspec(dllexport) __cdecl get(HWND hwndParent, int string_size, TCHAR variables, stack_t stacktop, extra_parameters extra ){ HANDLE hThread; DWORD dwThreadId; MSG msg; TCHAR szUsername[64]=TEXT(""), // proxy params szPassword[64]=TEXT(""); EXDLL_INIT(); g_pluginExtra = extra; g_hwndParent = hwndParent; TCHAR variable0 = getuservariable(INST_R0); // variable0被赋的值是NSIS代码中,获取到的系统版本数据 $R0,此处仅作为验证variables的获取 //以下是对命令行的解析 while(!popstring(url) && url == TEXT('/')) { //MessageBox(NULL, url, "test", MB_OK); if(lstrcmpi(url, TEXT("/silent")) == 0) silent = true; else if(lstrcmpi(url, TEXT("/weaksecurity")) == 0) g_ignorecertissues = true; else if(lstrcmpi(url, TEXT("/caption")) == 0) popstring(szCaption); else if(lstrcmpi(url, TEXT("/username")) == 0) popstring(szUsername); else if(lstrcmpi(url, TEXT("/password")) == 0) popstring(szPassword); ………………此处代码省略,便于阅读 else if(lstrcmpi(url, TEXT("/translate")) == 0) { if(popup) { popstring(szUrl); popstring(szStatus[ST_DOWNLOAD]); // Downloading popstring(szStatus[ST_CONNECTING]); // Connecting lstrcpy(szStatus[ST_URLOPEN], szStatus[ST_CONNECTING]); popstring(szDownloading);// file name popstring(szConnecting);// received popstring(szProgress);// file size popstring(szSecond);// remaining time popstring(szRemaining);// total time } else { popstring(szDownloading); popstring(szConnecting); popstring(szSecond); popstring(szMinute); popstring(szHour); popstring(szPlural); popstring(szProgress); popstring(szRemaining); } } else if(lstrcmpi(url, TEXT("/banner")) == 0) { popup = true; szBanner = (TCHAR)LocalAlloc(LPTR, string_size sizeof(TCHAR)); popstring(szBanner); } else if (lstrcmpi(url, TEXT("/callback")) == 0) { //新增对callback命令的解析,获取回调函数地址; g_progressCallback = popint(); TCHAR buf12 = (TCHAR)LocalAlloc(LPTR, 8 sizeof(TCHAR)); wsprintf(buf12, TEXT("%d"), g_progressCallback); } ……………… } pushstring(url);…………}
插件执行下载时,下载中状态会调用fileTransfer在fileTransfer方法中,添加回调方法的调用,实现对下载进度的捕获void fileTransfer(HANDLE localFile, HINTERNET hFile){ static BYTE data_buf[10248]; BYTE dw; DWORD rslt = 0; DWORD bytesDone; status = ST_DOWNLOAD; while(status == ST_DOWNLOAD) { if(fput) { if(!ReadFile(localFile, data_buf, rslt = sizeof(data_buf), &bytesDone, NULL)) { status = ERR_FILEREAD; break; } if(bytesDone == 0) // EOF reached { status = ST_OK; break; } while(bytesDone > 0) { dw = data_buf; if(!InternetWriteFile(hFile, dw, bytesDone, &rslt) || rslt == 0) { status = ERR_TRANSFER; break; } dw += rslt; cnt += rslt; bytesDone -= rslt; } //MessageBox(NULL, "fput", "progress", MB_OK); } else { if(!InternetReadFile(hFile, data_buf, sizeof(data_buf), &rslt)) { status = ERR_TRANSFER; break; } if(rslt == 0) // EOF reached or cable disconnect { // on cable disconnect returns TRUE and 0 bytes. is cnt == 0 OK (zero file size)? // cannot check this if reply is chunked (no content-length, http 1.1) status = (fs != NOT_AVAILABLE && cnt < fs) ? ERR_TRANSFER : ST_OK; break; } if(szToStack) { for (DWORD i = 0; cntToStack < g_stringsize && i < rslt; i++, cntToStack++) if (convToStack) ((BYTE)szToStack + cntToStack) = data_buf[i]; // Bytes else (szToStack + cntToStack) = data_buf[i]; // ? to TCHARs } else if(!WriteFile(localFile, data_buf, rslt, &bytesDone, NULL) || rslt != bytesDone) { status = ERR_FILEWRITE; break; } cnt += rslt; //MessageBox(NULL, "not fput", "progress", MB_OK); } //此处实现对下载进度的回调 if (g_progressCallback != -1) { static TCHAR buf[32]; wsprintf(buf, TEXT("%lu"), cnt / 1024); pushstring(buf); wsprintf(buf, TEXT("%lu"), fs != NOT_AVAILABLE ? fs / 1024 : 0); pushstring(buf); wsprintf(buf, TEXT("%lu"), fs > 0 && fs != NOT_AVAILABLE ? MulDiv(100, cnt, fs) : 0); //MessageBox(NULL, buf, "progress", MB_OK); pushstring(buf); g_pluginExtra->ExecuteCodeSegment(g_progressCallback - 1, g_hwndParent); } }}
至此对intec的改造完成,主要是获取下载进度,同步到NSIS中的进度条中,具体的进度计算方法可以自己定义下载好的7z压缩包,可以使用nsis7z插件进行解压,从而实现网络安装NSIS7Z plug-in :http://nsis.sourceforge.net/Nsis7z_plug-in回调函数的传递,也可以不通过添加命令行比如在插件C++代码,直接通过getuservariable函数获取,另外也可以根据插件提供的/TRANSLATE 命令传递,示例如下:具体实现方式我尚未探索,只是作为一个思路提供出来intec的用法和nsisdl类似:LangString DESC_REMAINING ${LANG_ENGLISH} " (%d %s%s remaining)"LangString DESC_PROGRESS ${LANG_ENGLISH} "%d.%01dkB/s" ;"%dkB (%d%%) of %dkB @ %d.%01dkB/s"LangString DESC_PLURAL ${LANG_ENGLISH} "s"LangString DESC_HOUR ${LANG_ENGLISH} "hour"LangString DESC_MINUTE ${LANG_ENGLISH} "minute"LangString DESC_SECOND ${LANG_ENGLISH} "second"LangString DESC_CONNECTING ${LANG_ENGLISH} "Connecting..."LangString DESC_DOWNLOADING ${LANG_ENGLISH} "Downloading %s"LangString DESC_SHORTDOTNET ${LANG_ENGLISH} "Microsoft .Net Framework 1.1"LangString DESC_LONGDOTNET ${LANG_ENGLISH} "Microsoft .Net Framework 1.1"LangString DESC_DOTNET_DECISION ${LANG_ENGLISH} "$(DESC_SHORTDOTNET) is required.$\nIt is strongly \ advised that you install$\n$(DESC_SHORTDOTNET) before continuing.$\nIf you choose to continue, \ you will need to connect$\nto the internet before proceeding.$\nWould you like to continue with \ the installation?"LangString SEC_DOTNET ${LANG_ENGLISH} "$(DESC_SHORTDOTNET) "LangString DESC_INSTALLING ${LANG_ENGLISH} "Installing"LangString DESC_DOWNLOADING1 ${LANG_ENGLISH} "Downloading"LangString DESC_DOWNLOADFAILED ${LANG_ENGLISH} "Download Failed:"LangString ERROR_DOTNET_DUPLICATE_INSTANCE ${LANG_ENGLISH} "The $(DESC_SHORTDOTNET) Installer is \ already running."LangString ERROR_NOT_ADMINISTRATOR ${LANG_ENGLISH} "$(DESC_000022)"LangString ERROR_INVALID_PLATFORM ${LANG_ENGLISH} "$(DESC_000023)"LangString DESC_DOTNET_TIMEOUT ${LANG_ENGLISH} "The installation of the $(DESC_SHORTDOTNET) \ has timed out."LangString ERROR_DOTNET_INVALID_PATH ${LANG_ENGLISH} "The $(DESC_SHORTDOTNET) Installation$\n\ was not found in the following location:$\n"LangString ERROR_DOTNET_FATAL ${LANG_ENGLISH} "A fatal error occurred during the installation$\n\ of the $(DESC_SHORTDOTNET)."LangString FAILED_DOTNET_INSTALL ${LANG_ENGLISH} "The installation of $(PRODUCT_NAME) will$\n\ continue. However, it may not function properly$\nuntil $(DESC_SHORTDOTNET)$\nis installed."…………………… nsisdl::download /TRANSLATE "$(DESC_DOWNLOADING)" "$(DESC_CONNECTING)" \ "$(DESC_SECOND)" "$(DESC_MINUTE)" "$(DESC_HOUR)" "$(DESC_PLURAL)" \ "$(DESC_PROGRESS)" "$(DESC_REMAINING)" \ /TIMEOUT=30000 "$URL_DOTNET" "$PLUGINSDIR\dotnetfx.exe" DetailPrint "$(DESC_DOWNLOADING1) $(DESC_SHORTDOTNET)..."
以上就是开发NSIS插件的主要知识,实际实践过程需要在踩坑的过程中磨砺自己的编程技巧另外补充一些与本次插件开发的一些知识点:1、NSIS获取系统版本和判断系统位数通过读取注册表,获取系统版本ReadRegStr $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion" VersionNumber引入:!include "x64.nsh"Section;64位系统 ${If} ${RunningX64};这里进行相应的操作 ${Else};这里进行相应的操作 ${EndIf}SectionEnd
2、C中获取char数组长度strlen只能用char做参数,且该char数组必须是以''/0''结尾的;sizeof 即使在字符数组没有终止符'/0' 的时候,也能够计算出数组“长度”的原因,但这里的“长度”实际上是:编译器分配给该数组变量的内存大小示例:char chs[] = {'a', 'c', '\0', 'z', '3','d'}; // sizeof(chs) = 6; 而strlen(chs) = 23、wsprintf用法sprintf 单字节版本的C/C++库函数swprintf 宽字节版本的C/C++库函数而我们上面的wsprintf和上面两个函数看起来很相似,大家不要搞混淆了啊,wsprintf最前面的w不是代表Wide,宽字节的意思了,而是Windows的W,代表是windows的API函数了,其实它是一个宏这在上面已经说过了,真正的API函数其实是wsprintfA和wsprintfW这两个,在不严格的情况下通常我们也说wsprintf是函数wsprintf(缓冲区,格式,要格式化的值);wsprintf只能输出字符,字符串和整型数据,要输出任意类型应该用swprintf3002关于格式:%d:输入输出为整形 %ld 长整型 %hd短整型 %hu无符号整形 %u %lu%s:输入输出为字符串 %c字符%f:输入输出为浮点型 %lf双精度浮点型参考文档:Inetc plug-in - NSIS (sourceforge.io)作者:夏继亮 来源-微信公众号:三七互娱技术团队出处:https://mp.weixin.qq.com/s/9ozdXD110i6Fw61VUStR3g
(图片来源网络,侵删)
0 评论