NSIS教程(7): 開發第三方外掛
阿新 • • 發佈:2018-12-31
一、外掛開發規範
NSIS外掛對開發語言沒有限制(本文基於C++語法開發),只要按照NSIS標準所要求的生成一個dll檔案,該dll檔案提供純C規範的
、固定格式的
匯出函式即可。
總結起來,開發NSIS外掛的步驟為:
-
使用Visual Studio新建dll工程(執行庫選擇
MT
或MTD
),因為使用的MT執行庫,對VS的版本沒有要求。 -
定義外掛功能函式,假設函式名為add,則函式宣告格式如下:
extern "C" __declspec(dllexport) void __cdecl add ( HWND hwndParent, int string_size, char *variables,
-
編譯生成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
)操作函式:getuservariable
,setuservariable
四、示例
基於上面的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
需要哪些工作了?
- 將外掛程式碼中的
char
改成wchar_t
。 - 在nsi指令碼中加入
Unicode True
。 - 需要將生成的外掛dll放到nsis目錄下的
Plugins\x86-unicode
子目錄中。