In his book Programming Applications for Microsoft Windows Jeffrey Richter (Richter, 1999, DLLs and a Process's Address Space) defines a dynamic link library to be a set of source modules each of which contains a set of functions that another application or DLL can call. Ordinary libraries are statically linked upon program compilation but dynamic link libraries get loaded by the program at runtime. That's why updating the library doesn't require recompiling the whole program provided that the functions exported by the library remain unchanged in terms of interface. Another advantage of dynamic linking is sharing the same library between multiple programs rather transparently to the programmer.
When a process is using a dynamic link library, a new instance of the library is created and mapped to the address space of the client process. That's why the resources reserved by a dll, such as the memory being allocated, are taken from the client, that is the process using the dll. Similarly, even the global variables of a DLL are instance specific so they get allocated separately for each client process. In order to save both space and other resources, one must tell to the linker that certain global variables belong to a common data segment, shared by all the client processes. In Visual Studio 6 the directive is:
#pragma comment(linker,"/SECTION:shared,RWS")
AS has been mentioned before, system wide hooks must recide in dynamic link libraries. The reason is probably that without being able to access the client's memory space with the help of a DLL, you cannot intercept messages due to memory protection. That's why the library in the example application (hooklib.dll) also contains the hooks and most of the program logic. The easiest way of getting the main program to load hooklib.dll at runtime is to tell the existence of the library in the project linker options by adding the hooklib.lib library to the object/library modules list. To include hooklib.h in the main app, you give the #include "hooklib.h" pre-compiler directive as expected.
If you want a library function to be shown outside the library and to the main program as well, you must mention it in the library header (hooklib.h). This is done with an export definition which is different depending on whether you use a C or C plus plus compiler:
#ifdef __cplusplus #define EXPORT extern "C" __declspec (dllexport) #else #define EXPORT __declspec (dllexport) #endifHooklib.h has two public functions both of which are used in the main program. Below are the function declarations from the header (pay attention to the export definition):
EXPORT BOOL CALLBACK hooklibInitialize(HWND hWnd); EXPORT void CALLBACK hooklibCleanup();The Hooklib library has also got a shared datasegment for improving performance and saving memory.
// Shared data section that's global to all DLL instances.
#pragma data_seg ("shared")
HHOOK hCbtHook = NULL; // CBT-hook handle.
HHOOK hCallprocHook = NULL; // Callwndproc-hook handle.
// ...
#pragma data_seg ()
Upon running the main program, WIndows seeks and loads the library hooklib.dll automatically. It is possible to load and release libraries explicitly at runtime with the functions LoadLibrary and FreeLibrary but that's outside the scope of this essay.
When a process loads or stops using a dynamic link library, a special DllMain function, in which the programmer can do initialization and destruction, gets called. The fdReason parameter tells whether it is a process or a thread being removed (detached)or attached. A false return value from the function signals failed initialization or cleanup activities to Windows. In the example app, the code in the dll's main function only saves away the handle to the DLL-instance that the client process gets upon attaching the DLL.
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
if(fdwReason == DLL_PROCESS_ATTACH)
hDllInstance = hInstance; // Save current instance.
return TRUE;
} // main
Communication with the main program and the library is difficult, because there are several instances of the library in use in different processes (each process getting hooked). One relatively little known but easy way for inter-process communication is using the WM_DATA_COPY message. The message must be sent with the SendMessage function, which blocks the sender process until the message has been delivered, unlike the asynchronous, immediately returning PostMessage function. The wParam in the message is the sender's window handle and lParam contains a pointer to a COPYDATASTRUCT structure. The struct has a single DWORD-field for general information, a length field telling the size of the rest of the data and finally a void pointer to the real information being passed. In the example, a string gets passed from the library to the main program.
COPYDATASTRUCT data = {0, lstrlen(textOut) + 1, (PVOID) textOut};
SendMessage(hObserver, WM_COPYDATA, (WPARAM) hDllInstance, (LPARAM) &data);
The DWORD field isn't used (value 0). As lstrlen doesn't count the trailing null character, the size field must be increased by one. TCHAR character pointer is casted to PVOID that is a generic pointer type. In the main program, PVOID gets casted back to PTCHAR on processing the copy data message:
case WM_COPYDATA: // A message from hooklib.dll - display it. copyData = (COPYDATASTRUCT*) lParam; SetWindowText(textBox, (PTCHAR) copyData->lpData); return TRUE;