Writing NSIS Plugins
From Open Watcom
I am currently working on getting plugins compiled with Open Watcom to work with NSIS. The NSIS compiler is not loading the dlls I compile with Open Watcom. At the moment this page documents my research in this area. I am posting my progress to the newsgroup as well. Feel free to make notes on this page, the talk page or the newsgroup if you wish to be of assistance. --Zippy1981 23:47, 3 January 2007 (PST)
Contents |
Overview of NSIS plugins
NSIS plugins are simply DLLs. The functions provided by these functions are exported functions that have a specific array of parameters. That signature is as follows.
__declspec(dllexport) void __cdecl TestFunc(HWND hwndParent, int string_size,
char *variables, stack_t **stacktop)
Parameters are always passed to plugins as strings. int string_size, char *variables behave as int argc, char *argv[] do for main() in an executable.
How NSIS loads plugins
There is a file called Plugins.cpp in the NSIS source. This contains the class Plugins with the member Plugins::FindPlugins() that scans the plugins directory for dll files. FindPlugins() then calls Plugins::GetExports(), which scans the found dll files for NSIS plugin commands. These functions display their output via fprintf (g_output, ... ) calls. g_output is a global variable pointing to stdout in the command line compiler or the TextArea "console" on the gui compiler.
Getting a closer look
The Plugins class is very well written. As such it is trivial to wrote a program that uses this class and simply set g_output equal to 'stdout to list all the plugins functions that get loaded. I have developed such a program call DllScanner.
When I started this effort, it became apparent that that Plugins.cpp would not compile in OW without significant modification. Thanks to Peter Chapin for making that explicitly clear. Therefore I used Microsoft Visual C++ 2005 to compile the program.
Dllscanner.cpp contains the code I wrote. Its contents are below. I linked in Plugins.cpp and kept adding source files from NSIS until I stopped getting unresolved symbol errors from the linker. The contents of this file are below. After I got that working I make [[#Plugins::GetExports()]|GetExports()] be more verbose about what it was doing.
This verbosity has revealed that the Plugins class does not find an export header in the OpenWatcom DLL. The output for a Visual Studio compiled dll is as follows:
Scanning c:\Program Files\NSIS\Plugins\nsODBC.dll: - 4 sections found. - Exports found at section 1. - nsODBC::AddDSN - nsODBC::AddSysDSN - nsODBC::ConfDSN - nsODBC::ConfSysDSN - nsODBC::RemoveDSN - nsODBC::RemoveDefDSN - nsODBC::RemoveSysDSN
For an OpenWatcom dll the results are as follows:
Output for a watcom compiled dll is as follows. Scanning c:\Program Files\NSIS\Plugins\noname.dll: - 5 sections found.
The message Exports found at section n. is displayed upon success of this if statement:
if
(va <= ExportDirVA &&
va + FIX_ENDIAN_INT32(sections[i].Misc.VirtualSize)
>=
ExportDirVA + ExportDirSize)
The values of the variables in that statement are:
va Virtual address of the current section
ExpirtDirVA The virtual address of the Export Directory
sections[] An array of IMAGE_SECTION_HEADER(s) This is gotten by
calling IMAGE_FIRST_SECTION() on the PIMAGE_NT_HEADERS
ExportDirSize The size of the export data dirctory.
The problem is obviously that the Headers of the DLL files are different from the ones generated by Visual Studio. I just don't know why.
DllScanner Source code
DllScanner.cpp
Contents of the file:
#include <stdio.h>
#include <stdlib.h>
#include "Plugins.h"
using namespace std;
FILE *g_output = stdout;
int g_display_errors = true;
void main (int argc, char *argv[]) {
printf ("DLL Scanner for NSIS.\n");
if (argc != 2) {
fprintf (stderr, "\tUsage:\n\t\tDllScanner PathToDlls\n");
exit (1);
}
Plugins oPlugins;
string *searchPath;
searchPath = new string(argv[1]);
oPlugins.FindCommands(*searchPath, true);
return;
}
Plugins::GetExports()
void Plugins::GetExports(const string &pathToDll, bool displayInfo)
{
fprintf (g_output, "Scanning %s:\n", pathToDll.c_str());
vector<unsigned char> dlldata;
PIMAGE_NT_HEADERS NTHeaders;
try {
dlldata = read_file(pathToDll);
NTHeaders = CResourceEditor::GetNTHeaders(&dlldata[0]);
} catch (std::runtime_error& err) {
fprintf (g_output, "%s\n", err.what());
return;
}
const string dllName = remove_file_extension(get_file_name(pathToDll));
FIX_ENDIAN_INT16_INPLACE(NTHeaders->FileHeader.Characteristics);
if (NTHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL)
{
FIX_ENDIAN_INT32_INPLACE(NTHeaders->OptionalHeader.NumberOfRvaAndSizes);
if (NTHeaders->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) {
fprintf
(g_output, "The statement %s evaluated to non zero.\n\tI'll be damned if I know what that means",
"(NTHeaders->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT)");
return;
}
DWORD ExportDirVA = NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
DWORD ExportDirSize = NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(NTHeaders);
FIX_ENDIAN_INT32_INPLACE(ExportDirVA);
FIX_ENDIAN_INT32_INPLACE(ExportDirSize);
WORD num_sections = FIX_ENDIAN_INT16(NTHeaders->FileHeader.NumberOfSections);
fprintf(g_output, "- %d sections found.\n", num_sections);
for (DWORD i = 0; i < num_sections; i++)
{
DWORD va = FIX_ENDIAN_INT32(sections[i].VirtualAddress);
if (va <= ExportDirVA
&& va + FIX_ENDIAN_INT32(sections[i].Misc.VirtualSize) >= ExportDirVA + ExportDirSize)
{
fprintf(g_output, "- Exports found at section %d.\n", i);
DWORD prd = FIX_ENDIAN_INT32(sections[i].PointerToRawData);
PIMAGE_EXPORT_DIRECTORY exports = PIMAGE_EXPORT_DIRECTORY(&dlldata[0] + prd + ExportDirVA - va);
DWORD na = FIX_ENDIAN_INT32(exports->AddressOfNames);
unsigned long *names = (unsigned long*)((unsigned long) exports + (char *) na - ExportDirVA);
for (unsigned long j = 0; j < FIX_ENDIAN_INT32(exports->NumberOfNames); j++)
{
const string name = string((char*)exports + FIX_ENDIAN_INT32(names[j]) - ExportDirVA);
const string signature = dllName + "::" + name;
const string lcsig = lowercase(signature);
m_command_to_path[lcsig] = pathToDll;
m_command_lowercase_to_command[lcsig] = signature;
if (displayInfo)
fprintf(g_output, " - %s\n", signature.c_str());
}
break;
}
}
} else {
fprintf(g_output, "Does not appear to be a valid DLL\n");
}
}
References
You need the following to build NSIS yourself.
- getting NSIS source from CVS
- Or just download a tarball of the source
- Python download page
- SCons Download. Do not download the version off the home page. It is to old to build NSIS

