mIRC ScriptBox Tutorial: How To Make A mIRC Bot
mScriptBox Tutorial
Using and creating mIRC DLLs

Written by Necroman
Published with permission

Table Of Contents

  1. Introduction
  2. Using DLLs
  3. Choosing a compiler
  4. Creating a DLL in Visual C++ - step by step instructions
  5. The structure of a mIRC DLL
  6. The DEF file
  7. LoadDll, UnloadDll
  8. Input arguments and return values
  9. SendMessage
  10. Optimiziation
  11. nLINKn - A simple DLL for managing shortcuts


1. Introduction  Back to Top

As any other scripting language, the built-in mIRC language is not ideal. For some tasks it is simple and effective, for others it's slow and awkward. It limits your possibilities to the subset of the Windows features - such is the price for its simplicity.

Fortunately, nobody forces you to retire into the shell of mIRC aliases, no matter how numerous they might be. You can use DLLs (Dynamic-Link Libraries) from your script, which opens a lot of promising opportunities. You can think of a DLL as a "portal to the outer world", a feature that allows you to utilize modern technologies to bring the users of your script into comfortable, appealing environment.

What is a DLL?

This is how Microsoft answers this question:

Dynamic-link libraries (DLL) are modules that contain functions and data. A DLL is loaded at run time by its calling modules (.exe or .dll). DLLs can define two kinds of functions: exported and internal.

  • The exported functions can be called by other modules.
  • Internal functions can only be called from within the DLL where they are defined.
  • So, DLLs are nothing but collections of functions available for scripters.

    DLLs can be fast, very fast. If you need to compress huge amounts of data or display 3D graphics you simply have no other alternatives.

    2. Using DLLs  Back to Top

    Before attempting to create a DLL you must know how to use one.

    DLLs do not need to be explicitly "installed" - mIRC loads a DLL immediately after you call one of its functions. To call a function, use

    /dll dll_full_filename function parameters
    or
    $dll(dll_full_filename,function,parameters)

    Unlike /dll, $dll can return a value, like other identifiers.

    A DLL may choose to stay in memory after a call or allow mIRC to unload it. If the DLL is not used for ten minutes, mIRC will make another attempt to unload it. Again, the DLL may decide to stay loaded.

    To display a list of all loaded DLLs type:

    //VAR %c = $dll(0) | WHILE %c { echo -a $dll(%c) | DEC %c }

    To unload a DLL by force, use /dll -u. To unload all DLLs type:

    //VAR %c = $dll(0) | WHILE %c { dll -u $dll(%c) | DEC %c }

    There is currently no naming convention for DLL functions - their authors give them whatever names they prefer. Most DLLs contain a function like "DLLInfo" or "version" which you can use to obtain information about the DLL and its author.

    If the number of the possible return values is limited, the function is likely to return a string like E_INVALID_WINDOW, E_NOT_FOUND, S_FALSE, S_OK, etc. The first character indicates error (E) or success (S), the rest of the string is a human-readable message.

    It is always a good idea to check return values:

    var %result = $dll(my.dll,function,parameters)
    IF (E_* !iswm %result) { do_something } | ELSE { echo -a error: %result }
    


    3. Choosing a compiler  Back to Top

    You can create DLLs in different programming languages, within different development environments - it simply matters nothing for mIRC as they are all compiled into machine code. You cannot create a DLL if you cannot program in C/C++, Pascal, Basic or another language. This tutorial cannot teach you how to write programs, it assumes you already know that.

    Your compiler must be able to create Win32 DLLs. The most popular ones are Visual C++, Delphi, LCC, Borland C++ Builder.

    Windows itself resides in a few DLLs written in Assembly and C, and all its documentation (Microsoft Developer Network - www.msdn.microsoft.com) uses the C syntax. DLLs written in C/C++ can be somewhat smaller and faster, but it's easy to misuse these powerful languages, which results in crashes and memory leaks. High-level languages like Pascal make the process of creating DLLs more rapid - instead of thinking of memory allocation and stuff like that you can concentrate on your task.

    So, the general rule is "use what you like best". If you don't know what to choose, ask your friends. If you use the same compiler they'll be able to help you when you're stuck.

    With Visual C++ you have another alternative - whether to use MFC or not. MFC is more suitable for large-scale applications while mIRC DLLs tend to be as small as possible. I don't use MFC in this tutorial.


    4. Creating a DLL in Visual C++  Back to Top

    1. In the Visual C++ 6.0 "File" menu, choose "New". In the list of projects choose "Win32 Dynamic-Link Library" and enter the name of your DLL, for example, "nLINKn" (without quotes).

      Click "OK" and choose "Empty project".

    2. Add a .cpp file into your project: choose "File/New", "C++ Source File", and enter the name of your source file, for example, "nLINKn.cpp".

    3. Add a .def file into your project: choose "File/New", "Text File", and enter the name of your DEF file, for example, "nLINKn.def".
    4. Add the code for the functions to your .cpp file.
    5. Add the exported names to your .def file.
    6. Press F7 to compile your DLL.
    Steps 1-4 and 7 are pretty straightforward. We will discuss steps 5 and 6 in detail.

    Notice: If you have "played" with some DLLs you already know that an easy, clear interface is not less important than rich functionality. Always care about the person who will use your DLL.


    5. The structure of a mIRC DLL  Back to Top

    Every function accessible from mIRC must have the following prototype (see mirc.hlp):

    
    int WINAPI procname(HWND mWnd, HWND aWnd, char *data, char *parms, BOOL show, BOOL nopause)
    

    You should read mirc.hlp to find out what each of these parameters means. mWnd, for example, is the handle of the main mIRC window.

    The DLL can return an integer to indicate what it wants mIRC to do (again, see mirc.hlp). For example, the return value (int)3 means that the DLL has filled the "data" variable with the result that $dll() as an identifier should return.

    When creating a DLL in Visual C++ you must declare your functions as extern "C", otherwise their exported names will be "decorated".

    
    //Example: this function returns "Hello World": 
    #include 
    
    extern "C" int WINAPI func(HWND,HWND,char *data,char*,BOOL,BOOL)
    {
    	lstrcpy(data,"Hello World");
    	return 3;
    }
    

    If you add the function above to the .cpp file, it will be compiled successfully, but mIRC will not see it. To "export" (make accessible) it we will use our DEF file.


    6. The DEF file  Back to Top

    The DEF file specifies which functions are exported by the DLL, and their exported names.
    A typical DEF file looks like this:

    
    LIBRARY dll_name
    
    EXPORTS
    
    func1	= _func1@24
    func2 = _func2@24
    func3 = _func3@24
    
    LoadDll = _LoadDll@4
    UnloadDll = _UnloadDll@4
    

    The exported names are func1, func2, func3, LoadDll and UnloadDll, while _func1@24, _func2@24, _func3@24, _LoadDll@4 and _UnloadDll@4 are real names.

    As you see, Visual C++ adds the _ and @ symbols to real names. It also adds the number of bytes required for input arguments.

    All the standard mIRC functions accept 6 arguments, therefore their real names will always look like _name@24. The LoadDll and UnloadDll functions only accept 1 argument, that's why their names will always be _LoadDll@4 and _UnloadDll@4.

    A function may be aliased, that is, it may be exported under several names:

    
    version	= _version@24
    DLLInfo	= _version@24
    lnVersion	= _version@24
    

    Given the example above, the following mIRC commands will produce the same result:

    
    echo -a $dll(dllname,version,0)
    echo -a $dll(dllname,DLLInfo,0)
    echo -a $dll(dllname,lnVersion,0)
    

    Notice that the function body in the .cpp file does not have to be duplicated 3 times. You only need to specify 3 different names for it in the .def file.


    7. LoadDll, UnloadDll  Back to Top

    By default a DLL is unloaded immediately after you make the /dll or $dll call. You can keep a DLL loaded by including a LoadDll routine in your DLL, which mIRC calls the first time you load the DLL:

    
    typedef struct {
    DWORD  mVersion;
    HWND   mHwnd;
    BOOL   mKeep;
    } LOADINFO;
    
    void __stdcall (*LoadDll)(LOADINFO*);
    

    mVersion contains the mIRC version number in the low and high words.

    mHwnd contains the window handle to the main mIRC window.

    mKeep is set to TRUE by default, indicating that mIRC will keep the DLL loaded after the call. You can set mKeep to FALSE to make mIRC unload the DLL after the call (which is how previous mIRCs worked).

    If your DLL does not export the LoadDll function, it is unloaded immediately after the call.

    You can also define an UnloadDll routine in your DLL which mIRC will call when unloading a DLL to allow it to clean up.

    
    int __stdcall (*UnloadDll)(int mTimeout);
    

    The mTimeout value can be:

    0 UnloadDll is being called due to a DLL being unloaded when mIRC exits, or unloaded with /dll -u.

    1 UnloadDll is being called due to a DLL not being used for ten minutes. The UnloadDll routine can return 0 to keep the DLL loaded, or 1 to allow it to be unloaded.

    Always include the LoadDll and UnloadDll functions into your DLLs unless you worry about memory usage. Constant loading and unloading slows the DLL down. If your DLL uses resources from its own module (icons, dialogs, etc.) and mIRC unloads it, Windows kernel will crash.


    8. Input arguments and return values  Back to Top

    mIRC passes input arguments in the string form, in the "data" parameter. "data" points to a buffer allocated by mIRC, its length is about 900 bytes. The input string is null-terminated. If the function wants to return information to mIRC, it copies the return string (also null-terminated) into the buffer.

    
    //Example:
    //   This function changes the titlebar of a window
    //   the first word is interpreted as the handle of the window, 
    //   the rest of the string is the new titlebar text
    
    #include <windows.h>
    
    extern "C" int WINAPI title(HWND,HWND,char *data,char*,BOOL,BOOL)
    {
    	HWND window = (HWND)atol(data);        //convert string into number
    	char *p = data;
    	
    //skip the first word
    if (*p == '-') p++;                        //skip 'minus' in negative numbers
    	while (*p >= '0' && *p <= '9') p++; //skip digits
    	while (*p == ' ') p++;	               //skip spaces
    	
    if (IsWindow(window))
    	{
    		SetWindowText(window,p);          //change the titlebar
    	lstrcpy(data,"S_OK");                 //fill the buffer with "S_OK"
    }
    else lstrcpy(data,"E_INVALID_WINDOW");    //fill the buffer with "E_INVALID_WINDOW"
    	return 3;                             //return mIRC value
    }
    


    9. SendMessage  Back to Top

    Another possibility for a DLL to communicate with mIRC is the SendMessage function. By sending messages to mIRC an application (or a DLL) can execute mIRC commands, evaluate identifiers and call aliases.

    Before using the SendMessage function a DLL must create a file mapping - a shared object where mIRC searches for commands and where it copies the results.

    The SendMessage function is described in the mIRC help file. To read more about file mappings visit
    http://msdn.microsoft.com/library/default.asp?URL=/library/psdk/winbase/filemap_79wn.htm

    The following example contains the function "call_alias" that calls the mIRC alias specified in the input parameter:

    
    #include <windows.h>
    
    HANDLE hFileMap;
    LPSTR mData;
    
    extern "C"
    {
      void WINAPI LoadDll(void*)
      {
    	//create file mapping
    	hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE,0,PAGE_READWRITE,0,4096,"mIRC");	
    	mData = (LPSTR)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS,0,0,0);
      }
    
      int WINAPI UnloadDll(int timeout)
      {
    	if (!timeout)
    	{
            //close file mapping
            UnmapViewOfFile(mData);
            CloseHandle(hFileMap);
    	}
        return 0;
      }
    
      int WINAPI call_alias(HWND mWnd,HWND aWnd,char *data,char*,BOOL,BOOL)
      {
        wsprintf(mData,"$%s(Hello mIRC!)",data);     //copy "$alias(Hello mIRC!)" into the file mapping
        SendMessage(mWnd,	WM_USER + 201,0,0);      //evaluate it
        MessageBox(aWnd,mData,"return value",MB_OK); //display return value
        lstrcpy(data,"S_OK"); return 3;              //return to mIRC
      }
    }
    


    10. Optimization  Back to Top

    Once you've compiled your first DLL, you might have been disappointed by its size. Even "Hello World" is ridiculously bloated.

    First of all, make sure your DLL does not contain debugging information.

  • Go to "Build/Set Active Configuration…" and choose "Release".
  • Also, in "Project/Settings…" change "Use run-time library:" from "Multithreaded" to "Release single threaded". Usually DLLs should use the multithreaded RTL, but since mIRC (5.9 and older) executes /dll and $dll in a single thread, you can safely use the single-threaded RTL (it is smaller and faster).
  • Press F7 to rebuild your module.
  • To make your DLL as small as possible, follow these steps:

    1. Press Alt+F7; set "Optimizations" to "Customize". Choose "assume no aliasing", "global optimizations", "favor small code" and "frame pointer omission".
    2. In "Customize", check "eliminate duplicate strings", "enable function level linking".
    3. In linker options, check "Ignore all default libraries". This will remove all the C run-time libraries from your module. You will have to add the DLL entry point:
      
      extern "C" int WINAPI _DllMainCRTStartup(HANDLE,DWORD,void*) { return 1; }
      
      You will be unable to use standard functions like "strcpy", "itoa", etc. Use their WinAPI equivalents, like "lstrcpy", "wsprintf" or write your own procedures.
    4. If you using C++ objects, add 3 functions to your code:
      
      extern int __cdecl _purecall() { return 0; }
      void* __cdecl operator new(size_t n) { return HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,n);
      void __cdecl operator delete(void* p) { if (p) HeapFree(GetProcessHeap(),0,p);
      
    5. Add /OPT: NOWIN98 to the linker options. This will decrease the padding size from 4096 to 512, greatly reducing the size of the executable.
    6. Add /Gs32700 to the compiler options. It will disable stack probes.

    With these tricks your DLL can be a few kilobytes in size.


    11. nLINKn - A simple DLL for managing shortcuts  Back to Top

    In this chapter we will create a simple DLL called nLINKn that can create, edit and resolve shell links (shortcuts). The DLL will have 7 functions:

    load, save

    The "load" function loads a link from a .lnk file into memory where it can be examined/modified. The "save" function saves the link from memory to a .lnk file. Both functions interpret the input string as the full name of the .lnk file:

    
    echo -a $dll(nLINKn,load,c:\mylink.lnk)
    echo -a $dll(nLINKn,save,c:\mylink2.lnk)
    
    resolve

    The "resolve" function tries to find the object the link in memory points to, even if it was renamed or moved. It interprets the input argument as the number of seconds the system is allowed to search for the object. The default is 3 seconds.

    
    echo -a $dll(nLINKn,resolve,5)
    
    set, get

    The "set" and "get" functions operate on various information fields of the link in memory.

    Valid fields are: l(abel), a(rguments), d(irectory), p(ath), k(ey), s(how), i(con).

    
    echo -a $dll(nLINKn,get,path)
    echo -a $dll(nLINKn,get,args)
    echo -a $dll(nLINKn,get,dir)
    echo -a $dll(nLINKn,get,label)
    echo -a $dll(nLINKn,get,key)
    echo -a $dll(nLINKn,get,show)
    echo -a $dll(nLINKn,get,icon)
    

    All changes made by the "set" function are only applied after you call the "save" function.

    specdir

    The "specdir" function returns the paths to a system or special Windows directory. This can be useful when you want to create a shortcut on the desktop, in the Startup folder, etc. The input parameter can be one of the following numbers:
     
     System  -2
     Windows  -1
     Desktop  0
     Programs  2
     My Documents  5
     Favorites  6
     Startup  7
     Recent  8
     SendTo  9
     Start Menu  11
     
     Desktop  16
     Nethood  19
     Fonts  20
     Templates  21
     Application Data  26
     Printhood  27
     Internet Cache  32
     Cookies  33
     History  34

    For example, this code creates a shortcut on the desktop:

    
    echo -a $dll(nLINKn,set,path c:\mydir\myscript.exe)
    echo -a $dll(nLINKn,set,dir c:\mydir)
    echo -a $dll(nLINKn,set,arguments /p /h)
    echo -a $dll(nLINKn,set,label this is optional)
    echo -a $dll(nLINKn,set,key 56)
    echo -a $dll(nLINKn,set,show 2)
    echo -a $dll(nLINKn,set,icon 25 $dll(nLINKn,specdir,-2) $+ \shell32.dll)
    echo -a $dll(nLINKn,save,$dll(nLINKn,specdir,0) $+ \mylink.lnk)
    
    version

    The "version" function provides information about the DLL and its author.

    
    echo -a $dll(nLINKn,version,0)
    

    It may be difficult for you to understand the code if you know nothing about COM. To learn Component Object Model, visit
    http://msdn.microsoft.com/library/default.asp?URL=/library/books/inole/trib194new.htm.

    In short, COM objects are simply .EXE or .DLL files. The COM technology provides means by which COM objects can communicate with each other, whether they be in the same process, in different processes, or even on different computers.

    The main idea of COM is that you are not allowed to call object methods directly. Instead, you must query an object for an interface you are interested in. If the object implements that interface, it gives you a pointer to it. If it does not implement it, you're out of luck.

    Physically an interface is an array of addresses to functions. The point of all this is incapsulation and safety - if you don't have a pointer to an interface, you cannot call its functions.

    You create COM objects with the CoCreateInstance API function. It needs to know the class of the object being created. COM classes are not human-readable; they look like {0713E8A2-850A-101B-AFC0-4210102A8DA7}.

    Of course, you don't have to remember such weird names - they are already in the Windows header files. All you need is to tell the CoCreateInstance function that you want to create an object belonging to a class like CLSID_WebBrowser, CLSID_DirectDraw or CLSID_ShellLink, and it will search trough the registry, start the appropriate .dll and .exe, find its class factory, create an object, query it for the requested interface and pass the pointer back to you. Piece of cake.


    Here is the contents of the nLINKn.DEF file:

    
    LIBRARY nLINKn
    EXPORTS
    LoadDll     = _LoadDll@4
    UnloadDll   = _UnloadDll@4
    resolve     = _resolve@24
    load        = _load@24
    save        = _save@24
    set         = _set@24
    get         = _get@24
    specdir     = _specdir@24
    version     = _version@24
    

    Here is the code in the nLINKn.CPP file:

    
    ////////////////////////////////////////////////////////////////////////////////////////////
    // nLINKn 1.0 - a DLL for creating, resolving and editing shell links (shortcuts)
    ////////////////////////////////////////////////////////////////////////////////////////////
    
    //the WIN32_LEAN_AND_MEAN macro excludes rarely used stuff from , such as
    //Korean language support, making compilation faster
    
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
    
    //these are pointers to the IShellLink and IPersistFile interfaces
    //implemented by the CLSID_ShellLink system object
    //we include <shlobj.h> because IShellLink and IPersistFile are declared there
    
    #include <shlobj.h>
    IShellLink *psl;
    IPersistFile *ppf;
    
    //this macro helps us return values to mIRC
    //instead of writing
    //		lstrcpy(data,"S_OK");
    //		return 3;		
    //we will be able to use
    //		ret("S_OK");
    
    #define ret(x) { lstrcpy(data,x); return 3; }
    
    //the entry point of the DLL
    
    extern "C" int WINAPI _DllMainCRTStartup(HANDLE,DWORD,void*) { return 1; }
    
    //since in the Options we have disabled the C run-time library,
    //we must write the "atol" function ourselves (it converts character strings into numbers)
    
    int atol(char *p)
    {
    	int result = 0, sign = 1;
    	if (*p == '-') { sign = -1; p++; }
    	while (*p >= '0' && *p <= '9') { result *= 10; result += (*p++ - '0'); }
    	return result * sign;
    }
    
    extern "C"
    {
    	//this function is called by mIRC after it loads the DLL
    	//we perform all the initialization here, which includes:
    	//		1. CoInitialize - initializes the Component Object Library
    	//		2. CoCreateInstace - creates the CLSID_ShellLink system object as an in-process server,
    	//			and gets a pointer to its IShellLink interface
    	//		3. QueryInterface - gets a pointer to the IPersistFile interface
    
    	void WINAPI LoadDll(void*)
    	{
    		CoInitialize(0);
    		CoCreateInstance(CLSID_ShellLink,0,CLSCTX_INPROC_SERVER,IID_IShellLink,(void**)&psl);
    		psl->QueryInterface(IID_IPersistFile,(void**)&ppf); 
    	}
    	
    	//this function is called by mIRC when it wants to unload the DLL
    	//if "timeout" is not 0, we can return 0 to stay loaded,
    	//otherwise we release the interface pointers and shutdown COM
    
    	int WINAPI UnloadDll(int timeout)
    	{
    		if (!timeout) { ppf->Release(); psl->Release(); CoUninitialize(); }
    		return 0;
    	}
    
    	//this function returns information about the DLL and its author
    	//it is ABSOLUTELY necessary ;-)
    	
    	int WINAPI version(HWND,HWND,char *data,char*,BOOL,BOOL)
    	{
    		ret("nLINKn 1.0 by Necroman, http://necroman.wot.net, necroman@europe.com, #mIRC @ Undernet");
    	}
    
    	//this function converts the filename into Unicode and calls IPersistFile::Save
    	
    	int WINAPI save(HWND,HWND,char *data,char*,BOOL,BOOL)
    	{
    		WCHAR filename[MAX_PATH]; MultiByteToWideChar(CP_ACP,0,data,-1,filename,MAX_PATH); 
    		ret(FAILED(ppf->Save(filename,TRUE)) ? "E_FAILED" : "S_OK");
    	}
    	
    	//this function converts the filename into Unicode and calls IPersistFile::Load
    	
    	int WINAPI load(HWND,HWND,char *data,char*,BOOL,BOOL)
    	{
    		WCHAR filename[MAX_PATH]; MultiByteToWideChar(CP_ACP,0,data,-1,filename,MAX_PATH); 
    		ret(FAILED(ppf->Load(filename,STGM_READ)) ? "E_FAILED" : "S_OK"); 
    	}
    	
    	//this function passes to IPersistFile::Resolve the maximum number of milliseconds
    	//allowed to resolve the link
    	
    	int WINAPI resolve(HWND,HWND,char *data,char*,BOOL,BOOL)
    	{
    		ret(FAILED(psl->Resolve(0,((atol(data) * 1000) << 16) | SLR_NO_UI)) ? "E_FAILED" : "S_OK"); 
    	}
    
    	//this function obtains the path to one of the system special folders
    	
    	//the SHGetSpecialFolderLocation function returns PIDL -
    	//the pointer to a list of identifiers in the system namespace
    	
    	//the SHGetPathFromIDList converts the PIDL into a string
    	//the PIDL is freed with IShellMalloc::Free
    
    	int WINAPI specdir(HWND,HWND,char *data,char*,BOOL,BOOL)
    	{
    		int folder = atol(data);
    		lstrcpy(data,"E_FAILED");	//assume failure
    		
    		if (folder == -1) { GetWindowsDirectory(data,MAX_PATH); return 3; }
    		if (folder == -2) { GetSystemDirectory(data,MAX_PATH); return 3; }
    		
    		LPITEMIDLIST pidl;
    		LPMALLOC pShellMalloc;
    		
    		if(SUCCEEDED(SHGetMalloc(&pShellMalloc)))
    		{
    			if(SUCCEEDED(SHGetSpecialFolderLocation(0,folder,&pidl)))
    			{
    				SHGetPathFromIDList(pidl,data);
    				pShellMalloc->Free(pidl);
    			}
    			pShellMalloc->Release();
    		}
    		return 3;
    	}
    
    	//this function reads a field in the shortcut in memory
    	//it interprets the first word as the name of the field to read
    	//then it calls one of the IShellLink methods
    	
    	int WINAPI get(HWND,HWND,char *data,char*,BOOL,BOOL)
    	{
    		HRESULT hres;
    		WIN32_FIND_DATA pfd;
    		WORD key;
    		int show;
    		char path[MAX_PATH];
    		
    		switch(*data)
    		{
    		case 'l':	//label
    			hres = psl->GetDescription(data,MAX_PATH); break;
    		case 'p':	//path
    			hres = psl->GetPath(data,MAX_PATH,&pfd,SLGP_UNCPRIORITY); break;
    		case 'a':	//arguments
    			hres = psl->GetArguments(data,MAX_PATH); break;
    		case 'd':	//directory
    			hres = psl->GetWorkingDirectory(data,MAX_PATH); break;
    		case 's':	//show
    			wsprintf(data,FAILED(psl->GetShowCmd(&show)) ? "E_FAILED" : "%d", show); return 3;
    		case 'i':	//icon
    			wsprintf(data,FAILED(psl->GetIconLocation(path,MAX_PATH,&show)) ? "E_FAILED" : "%d %s",show,path);
    			return 3;
    		case 'k':	//key
    			wsprintf(data,FAILED(psl->GetHotkey(&key)) ? "E_FAILED" : "%d",key); return 3;
    		default:
    			ret("E_UNKNOWN_FIELD");
    		}
    		if (FAILED(hres)) lstrcpy(data,"E_FAILED");
    		return 3;
    	}
    	
    	//this function overwrites information in a field of a shortcut in memory
    	//it interprets the first word as the name of the field to change,
    //passing the rest of the string to one of the IShellLink methods
    
    	int WINAPI set(HWND,HWND,char *data,char*,BOOL,BOOL)
    	{
    		HRESULT hres;
    		int icon;
    		char *p = data;
    		while (*p &&a,p; *p != ' ') p++; while (*p == ' ') p++; //skip field
    		
    		switch(*data)
    		{
    		case 'l': hres = psl->SetDescription(p);		break;
    		case 'k': hres = psl->SetHotkey(atol(p));	break;
    		case 'p': hres = psl->SetPath(p);			break;
    		case 's': hres = psl->SetShowCmd(atol(p));	break;
    		case 'a': hres = psl->SetArguments(p);		break;
    		case 'd': hres = psl->SetWorkingDirectory(p);	break;
    		case 'i': 
    			icon = atol(p);
    			while (*p >= '0' && *p <= '9') p++; while (*p == ' ') p++; //skip number
    			hres = psl->SetIconLocation(p,icon); break;
    		default:
    			ret("E_UNKNOWN_FIELD");
    		}
    		ret(FAILED(hres) ? "E_FAILED" : "S_OK");
    	}
    }
    

    The compiled DLL is about 4 kb in size and can be downloaded from http://necroman.wot.net/nLINKn_1.0.zip.