1. 程式人生 > >NSIS教程(7): 開發第三方外掛

NSIS教程(7): 開發第三方外掛

一、外掛開發規範

NSIS外掛對開發語言沒有限制(本文基於C++語法開發),只要按照NSIS標準所要求的生成一個dll檔案,該dll檔案提供純C規範的固定格式的匯出函式即可。

總結起來,開發NSIS外掛的步驟為:

  1. 使用Visual Studio新建dll工程(執行庫選擇MTMTD),因為使用的MT執行庫,對VS的版本沒有要求。

  2. 定義外掛功能函式,假設函式名為add,則函式宣告格式如下:

    extern "C" __declspec(dllexport) void __cdecl
    add ( HWND hwndParent, int string_size, char *variables,
    stack_t **stacktop, extra_parameters *extra);
  3. 編譯生成dll檔案。

二、引數、返回值

開發NSIS外掛的難點在於:獲取呼叫者傳入的引數,以及將執行結果返回給呼叫者。
前面的NSIS教程(6): 使用第三方外掛說到了,外掛函式的呼叫者傳遞引數方式有2種,一種是通過$0~$9 $R0~$R9的形式,這種形式的引數通過char *variables來獲取;另一種是通過堆疊的形式,這種形式的引數通過stack_t **stacktop來獲取。

關於傳入引數的獲取,執行結果的返回的具體過程,這裡不做分析了,下面第三節會給出一個通用的解決方案,對具體過程感興趣的,分析plugin-common.h

即可。

三、plugin-common.h

為了方便外掛開發中獲取使用者傳入引數、以及執行結果的返回,對常用操作的細節封裝到plugin-common.h檔案中,檔案內容如下:

#ifndef _PLUGIN_COMMON_H_
#define _PLUGIN_COMMON_H_

#include <stdio.h>

#define PLUGIN_BUF_LEN 1024

#define NSISAPI extern "C" __declspec(dllexport) void __cdecl


#define EXDLL_INIT()           {  \
        g_stringsize=string_size; \
        g_stacktop=stacktop;      \
        g_variables=variables; }
typedef struct _stack_t { struct _stack_t *next; char text[1]; } stack_t; static unsigned int g_stringsize; static stack_t **g_stacktop; static char *g_variables; enum { INST_0, // $0 INST_1, // $1 INST_2, // $2 INST_3, // $3 INST_4, // $4 INST_5, // $5 INST_6, // $6 INST_7, // $7 INST_8, // $8 INST_9, // $9 INST_R0, // $R0 INST_R1, // $R1 INST_R2, // $R2 INST_R3, // $R3 INST_R4, // $R4 INST_R5, // $R5 INST_R6, // $R6 INST_R7, // $R7 INST_R8, // $R8 INST_R9, // $R9 INST_CMDLINE, // $CMDLINE INST_INSTDIR, // $INSTDIR INST_OUTDIR, // $OUTDIR INST_EXEDIR, // $EXEDIR INST_LANG, // $LANGUAGE __INST_LAST }; static int __stdcall popstring(char *str) { stack_t *th; if (!g_stacktop || !*g_stacktop) return 1; th=(*g_stacktop); lstrcpyA(str,th->text); *g_stacktop = th->next; GlobalFree((HGLOBAL)th); return 0; } static void __stdcall pushstring(char *str) { stack_t *th; if (!g_stacktop) return; th=(stack_t*)GlobalAlloc(GPTR,sizeof(stack_t)+g_stringsize); lstrcpynA(th->text,str,g_stringsize); th->next=*g_stacktop; *g_stacktop=th; } static int __stdcall popint() { char buf[512] = {0}; popstring(buf); return atoi(buf); } static void __stdcall pushint(long value) { char buf[512] = {0}; sprintf_s(buf, "%ld", value); pushstring(buf); } static char * __stdcall getuservariable(int varnum) { if (varnum < 0 || varnum >= __INST_LAST) return NULL; return g_variables+varnum*g_stringsize; } static void __stdcall setuservariable(int varnum, char *var) { if (var != NULL && varnum >= 0 && varnum < __INST_LAST) lstrcpyA(g_variables + varnum*g_stringsize, var); } enum NSPIM { NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup NSPIM_GUIUNLOAD, // Called after .onGUIEnd }; typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM); // extra_parameters data structures containing other interesting stuff // but the stack, variables and HWND passed on to plug-ins. typedef struct { int autoclose; int all_user_var; int exec_error; int abort; int exec_reboot; // NSIS_SUPPORT_REBOOT int reboot_called; // NSIS_SUPPORT_REBOOT int XXX_cur_insttype; // depreacted int plugin_api_version; // see NSISPIAPIVER_CURR // used to be XXX_insttype_changed int silent; // NSIS_CONFIG_SILENT_SUPPORT int instdir_error; int rtl; int errlvl; int alter_reg_view; int status_update; } exec_flags_t; #ifndef NSISCALL #define NSISCALL __stdcall #endif typedef struct { exec_flags_t *exec_flags; int (NSISCALL *ExecuteCodeSegment)(int, HWND); void (NSISCALL *validate_filename)(char *); int (NSISCALL *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already registered and < 0 on errors } extra_parameters; #endif //_PLUGIN_COMMON_H_

我們開發外掛中經常用到的是:
4個堆疊操作函式:pushint,pushstring,popint,popstring
2個暫存器變數($R0~$R9,$0~$9)操作函式:getuservariablesetuservariable

四、示例

基於上面的plugin-common.h,我們可以將開發外掛的步驟流程化了,

#include <windows.h>
#include <commctrl.h>
#include <stdarg.h>
#include <tchar.h>
#include "plugin-common.h"

HINSTANCE g_hInstance;
HWND g_hwndParent;
extra_parameters *g_pluginParms = NULL;


#define NSMETHOD_INIT(parent) {\
        g_pluginParms = extra; \
        g_hwndParent = parent; \
        EXDLL_INIT(); }
		
		
BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved)
{
    g_hInstance = (HINSTANCE)hInst;

    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        //do what you want at init time.
    }

    if (ul_reason_for_call == DLL_THREAD_DETACH || ul_reason_for_call == DLL_PROCESS_DETACH) {
        //clean up code.
    }

    return TRUE;
}

//
// 以上內容通用,每個外掛都可以將上面的內容直接複製過去。
//


// NSIS外掛匯出函式,以add函式為例,若要新增其他函式,則只是函式名需要修改,函式邏輯寫在NSMETHOD_INIT(hwndParent);之後的花括號內。
//
extern "C" __declspec(dllexport) void __cdecl
add ( HWND hwndParent, int string_size, char *variables, stack_t **stacktop, extra_parameters *extra)
{
    NSMETHOD_INIT(hwndParent);
    {
        // == 新增自己程式碼
		// 假設呼叫者通過堆疊的形式傳遞的引數
        int i = popint();  // 注意:棧是先進後出
        int j = popint();
        int k = i + j;
        pushint(k); // 通過plugin-common.h中提供的pushint返回整數,也可以使用pushstring返回字串
        // ==
    }
}

五、Unicode支援

大家可能注意到了上面的示例和plugin-common.h中使用的都是char,而不是wchar_t。那如果要改成wchar_t需要哪些工作了?

  1. 將外掛程式碼中的char改成wchar_t
  2. 在nsi指令碼中加入Unicode True
  3. 需要將生成的外掛dll放到nsis目錄下的Plugins\x86-unicode子目錄中。