|< Dynamic Link Library, DLL 1 | Main | Dynamic Link Library, DLL 3 >| Site Index | Download |


 

 

 

 

MODULE CC

DYNAMIC LINK LIBRARY - DLL

Part 2: PROGRAM EXAMPLES

 

 

 

 

What do we have in this Module?

  1. Dynamic-Link Library and Windows APIs Story & Examples

  2. Types of Dynamic Linking

  3. Load-Time Dynamic Linking

  4. Run-Time Dynamic Linking

  5. DLLs and Memory Management

  6. Advantages of Dynamic Linking

  7. Dynamic-Link Library Entry-Point Function

  8. Calling the Entry-Point Function

  9. Entry-Point Function Definition

  10. Entry-Point Function Return Value

  11. Dynamic-Link Library Creation

  12. Creating Source Files

  13. Exporting Functions

  14. Exporting from a DLL Using __declspec(dllexport)

  15. Exporting C Functions for Use in C or C++ Language Executables

  16. Creating an Import Library

  17. Dynamic-Link Library Updates

  18. Dynamic-Link Library Redirection

  19. Dynamic-Link Library Data

  20. Variable Scope

  21. Dynamic Memory Allocation

  22. Thread Local Storage

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

My Training Period: zz hours. Before you begin, read some instruction here. The Windows MFC programming (GUI programming) for DLL can be found at MFC GUI Programming Step-by-step Tutorial.

 

The Win32 skills that supposed to be acquired:

Dynamic-Link Library and Windows APIs Story & Examples

 

Note: Some of the information provided in this Module is repetition of the previous Module. Tenouk would like to apologize.

 

A dynamic-link library (DLL) is a module that contains functions and data that can be used by another module (application or another DLL). A DLL can define two kinds of functions:

 

  1. Exported - is intended to be called by other modules, as well as from within the DLL where they are defined.

  2. Internal. - is typically intended to be called only from within the DLL where they are defined.

 

Although a DLL can export data, its data is generally used only by its functions. However, there is nothing to prevent another module from reading or writing that address. DLLs provide a way to modularize applications so that functionality can be updated and reused more easily. They also help reduce memory overhead when several applications use the same functionality at the same time, because although each application gets its own copy of the data, they can share the code.

The Windows application programming interface (API) is implemented as a set of dynamic-link libraries, so any process that uses the Windows API uses dynamic linking. Dynamic linking allows a module to include only the information needed to locate an exported DLL function at load time or run time. Dynamic linking differs from the more familiar static linking, in which the linker copies a library function's code into each module that calls it.

 

Types of Dynamic Linking

 

There are two methods for calling a function in a DLL:

 

  1. In load-time dynamic linking, a module (application or another module) makes explicit calls to exported DLL functions as if they were local functions. This requires you to link the module with the import library for the DLL that contains the functions. An import library supplies the system with the information needed to load the DLL and locate the exported DLL functions when the application is loaded.

  2. In run-time dynamic linking, a module (application or another module) uses the LoadLibrary() or LoadLibraryEx() function to load the DLL at run time. After the DLL is loaded, the module calls the GetProcAddress() function to get the addresses of the exported DLL functions. The module calls the exported DLL functions using the function pointers returned by GetProcAddress(). This eliminates the need for an import library.

 

Load-Time Dynamic Linking

 

When the system starts a program that uses load-time dynamic linking, it uses the information the linker placed in the file to locate the names of the DLLs that are used by the process. The system then searches for the DLLs in the following locations, in sequence:

 

  1. The directory from which the application loaded.

  2. The current directory.

  3. The system directory. Use the GetSystemDirectory() function to get the path of this directory.

  4. The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched. For Windows Me/98/95:  This directory does not exist.

  5. The Windows directory. Use the GetWindowsDirectory() function to get the path of this directory.

  6. The directories that are listed in the PATH environment variable. You can check the directories by typing PATH command at the command prompt.

 

For Windows Server 2003, Windows XP SP1:  The default value of HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode is 1 (current directory is searched after the System and Windows directories). For Windows XP:  If HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode is 1, the current directory is searched after the system and Windows directories, but before the directories in the PATH environment variable. The default value is 0 (current directory is searched before the system and Windows directories). Note that this value is cached on a per-process basis at load time.

If the system cannot locate a required DLL, it terminates the process and displays a dialog box that reports the error to the user. Otherwise, the system maps the DLL into the virtual address space of the process and increments the DLL reference count. The system calls the entry-point function. The function receives a code indicating that the process is loading the DLL. If the entry-point function does not return TRUE, the system terminates the process and reports the error. Finally, the system modifies the function address table with the starting addresses for the imported DLL functions. The DLL is mapped into the virtual address space of the process during its initialization and is loaded into physical memory only when needed.

 

Run-Time Dynamic Linking

 

When the application calls the LoadLibrary() or LoadLibraryEx() functions, the system attempts to locate the DLL using the same search sequence used in load-time dynamic linking. If the search succeeds, the system maps the DLL module into the virtual address space of the process and increments the reference count. If the call to LoadLibrary() or LoadLibraryEx() specifies a DLL whose code is already mapped into the virtual address space of the calling process, the function simply returns a handle to the DLL and increments the DLL reference count. Note that two DLLs that have the same base file name and extension but are found in different directories are not considered to be the same DLL.

The system calls the entry-point function in the context of the thread that called LoadLibrary() or LoadLibraryEx(). The entry-point function is not called if the DLL was already loaded by the process through a call to LoadLibrary() or LoadLibraryEx() with no corresponding call to the FreeLibrary() function.

If the system cannot find the DLL or if the entry-point function returns FALSE, LoadLibrary() or LoadLibraryEx() returns NULL. If LoadLibrary() or LoadLibraryEx() succeeds, it returns a handle to the DLL module. The process can use this handle to identify the DLL in a call to the GetProcAddress(), FreeLibrary(), or FreeLibraryAndExitThread() function.

The GetModuleHandle() function returns a handle used in GetProcAddress(), FreeLibrary(), or FreeLibraryAndExitThread(). The GetModuleHandle() function succeeds only if the DLL module is already mapped into the address space of the process by load-time linking or by a previous call to LoadLibrary() or LoadLibraryEx(). Unlike LoadLibrary() or LoadLibraryEx(), GetModuleHandle() does not increment the module reference count. The GetModuleFileName() function retrieves the full path of the module associated with a handle returned by GetModuleHandle(), LoadLibrary(), or LoadLibraryEx().

The process can use GetProcAddress() to get the address of an exported function in the DLL using a DLL module handle returned by LoadLibrary() or LoadLibraryEx(), GetModuleHandle(). When the DLL module is no longer needed, the process can call FreeLibrary() or FreeLibraryAndExitThread(). These functions decrement the module reference count and unmap the DLL code from the virtual address space of the process if the reference count is zero. Run-time dynamic linking enables the process to continue running even if a DLL is not available. The process can then use an alternate method to accomplish its objective. For example, if a process is unable to locate one DLL, it can try to use another, or it can notify the user of an error. If the user can provide the full path of the missing DLL, the process can use this information to load the DLL even though it is not in the normal search path. This situation contrasts with load-time linking, in which the system simply terminates the process if it cannot find the DLL. Run-time dynamic linking can cause problems if the DLL uses the DllMain() function to perform initialization for each thread of a process, because the entry-point is not called for threads that existed before LoadLibrary() or LoadLibraryEx() is called.

 

DLLs and Memory Management

 

Every process that loads the DLL maps it into its virtual address space. After the process loads the DLL into its virtual address, it can call the exported DLL functions. The system maintains a per-thread reference count for each DLL. When a thread loads the DLL, the reference count is incremented by one. When the process terminates, or when the reference count becomes zero (run-time dynamic linking only), the DLL is unloaded from the virtual address space of the process. Like any other function, an exported DLL function runs in the context of the thread that calls it. Therefore, the following conditions apply:

Advantages of Dynamic Linking

 

Dynamic linking has the following advantages over static linking:

  1. Multiple processes that load the same DLL at the same base address share a single copy of the DLL in physical memory. Doing this saves system memory and reduces swapping.

  2. When the functions in a DLL change, the applications that use them do not need to be recompiled or re-linked as long as the function arguments, calling conventions, and return values do not change. In contrast, statically linked object code requires that the application be re-linked when the functions change.

  3. A DLL can provide after-market support. For example, a display driver DLL can be modified to support a display that was not available when the application was initially shipped.

  4. Programs written in different programming languages can call the same DLL function as long as the programs follow the same calling convention that the function uses. The calling convention (such as C, Pascal, or standard call) controls the order in which the calling function must push the arguments onto the stack, whether the function or the calling function is responsible for cleaning up the stack, and whether any arguments are passed in registers. For more information, see the documentation included with your compiler.

 

A potential disadvantage to using DLLs is that the application is not self-contained; it depends on the existence of a separate DLL module. The system terminates processes using load-time dynamic linking if they require a DLL that is not found at process startup and gives an error message to the user. The system does not terminate a process using run-time dynamic linking in this situation, but functions exported by the missing DLL are not available to the program.

 

Dynamic-Link Library Entry-Point Function

 

A DLL can optionally specify an entry-point function. If present, the system calls the entry-point function whenever a process or thread loads or unloads the DLL. It can be used to perform simple initialization and cleanup tasks. For example, it can set up thread local storage when a new thread is created, and clean it up when the thread is terminated.

If you are linking your DLL with the C run-time library, it may provide an entry-point function for you, and allow you to provide a separate initialization function. Check the documentation for your run-time library for more information. If you are providing your own entry-point, see the DllMain() function in the following section. The name DllMain() is a placeholder for a user-defined function. You must specify the actual name you use when you build your DLL.

 

Calling the Entry-Point Function

 

The system calls the entry-point function whenever any one of the following events occurs:

  1. A process loads the DLL. For processes using load-time dynamic linking, the DLL is loaded during process initialization. For processes using run-time linking, the DLL is loaded before LoadLibrary() or LoadLibraryEx() returns.

  2. A process unloads the DLL. The DLL is unloaded when the process terminates or calls the FreeLibrary() function and the reference count becomes zero. If the process terminates as a result of the TerminateProcess() or TerminateThread() function, the system does not call the DLL entry-point function.

  3. A new thread is created in a process that has loaded the DLL. You can use the DisableThreadLibraryCalls() function to disable notification when threads are created.

  4. A thread of a process that has loaded the DLL terminates normally, not using TerminateThread() or TerminateProcess(). When a process unloads the DLL, the entry-point function is called only once for the entire process, rather than once for each existing thread of the process. You can use DisableThreadLibraryCalls() to disable notification when threads are terminated.

 

Only one thread at a time can call the entry-point function. The system calls the entry-point function in the context of the process or thread that caused the function to be called. This allows a DLL to use its entry-point function for allocating memory in the virtual address space of the calling process or to open handles accessible to the process. The entry-point function can also allocate memory that is private to a new thread by using thread local storage (TLS).

 

Entry-Point Function Definition

 

The DLL entry-point function must be declared with the standard-call calling convention (__stdcall). If the DLL entry point is not declared correctly, the DLL is not loaded, and the system displays a message indicating that the DLL entry point must be declared with WINAPI. For Windows Me/98/95:  If the DLL entry point is not declared correctly, the DLL is not loaded and the system displays a message titled "Error starting program," which instructs the user to check the file to determine the problem. In the body of the function, you may handle any combination of the following scenarios in which the DLL entry point has been called:

  1. A process loads the DLL (DLL_PROCESS_ATTACH).

  2. The current process creates a new thread (DLL_THREAD_ATTACH).

  3. A thread exits normally (DLL_THREAD_DETACH).

  4. A process unloads the DLL (DLL_PROCESS_DETACH).

 

The entry-point function should perform only simple initialization tasks. It must not call the LoadLibrary() or LoadLibraryEx() function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code. Similarly, the entry-point function must not call the FreeLibrary() function (or a function that calls FreeLibrary()), because this can result in a DLL being used after the system has executed its termination code.

It is safe to call other functions in Kernel32.dll, because this DLL is guaranteed to be loaded in the process address space when the entry-point function is called. It is common for the entry-point function to create synchronization objects such as critical sections and mutexes, and use TLS. Do not call the registry functions, because they are located in Advapi32.dll. If you are dynamically linking with the C run-time library, do not call malloc(); instead, call HeapAlloc().

Calling imported functions other than those located in Kernel32.dll may result in problems that are difficult to diagnose. For example, calling User, Shell, and COM functions can cause access violation errors, because some functions in their DLLs call LoadLibrary() to load other system components. The following example demonstrates how to structure the DLL entry-point function.

#include <windows.h>

 

BOOL WINAPI DllMain(

    HINSTANCE hinstDLL,  // handle to DLL module

    DWORD fdwReason,     // reason for calling function

    LPVOID lpReserved)     // reserved

{

    // Perform actions based on the reason for calling.

    switch(fdwReason)

    {

        case DLL_PROCESS_ATTACH:

            // Initialize once for each new process.

            // Return FALSE to fail DLL load.

            break;

        case DLL_THREAD_ATTACH:

            // Do thread-specific initialization.

            break;

        case DLL_THREAD_DETACH:

            // Do thread-specific cleanup.

            break;

        case DLL_PROCESS_DETACH:

            // Perform any necessary cleanup.

            break;

    }

     // Successful DLL_PROCESS_ATTACH.

    return TRUE;

}

Entry-Point Function Return Value

 

When a DLL entry-point function is called because a process is loading, the function returns TRUE to indicate success. For processes using load-time linking, a return value of FALSE causes the process initialization to fail and the process terminates. For processes using run-time linking, a return value of FALSE causes the LoadLibrary() or LoadLibraryEx() function to return NULL, indicating failure. The system immediately calls your entry-point function with DLL_PROCESS_DETACH and unloads the DLL. The return value of the entry-point function is disregarded when the function is called for any other reason.

 

Dynamic-Link Library Creation

 

To create a Dynamic-Link Library (DLL), you must create one or more source code files, and possibly a linker file for exporting the functions. If you plan to allow applications that use your DLL to use load-time dynamic linking, you must also create an import library.

 

Creating Source Files

 

The source files for a DLL contain exported functions and data, internal functions and data, and an optional entry-point function for the DLL. You may use any development tools that support the creation of Windows-based DLLs. If your DLL may be used by a multithreaded application, you should make your DLL "thread-safe". You must synchronize access to all of the DLL's global data to avoid data corruption. You must also ensure that you link only with libraries that are thread-safe as well. For example, Microsoft® Visual C++® contains multiple versions of the C Run-time Library, one that is not thread-safe and two that are. Please refer to Module A for more detail.

 

Exporting Functions

 

How you specify which functions in a DLL should be exported depends on the tools that you are using for development. Some compilers allow you to export a function directly in the source code by using a modifier in the function declaration. Other times, you must specify exports in a file that you pass to the linker. For example, using Visual C++, there are two possible ways to export DLL functions:

 

  1. With _declspec modifier or

  2. With a .def file.

 

If you use the _declspec modifier, it is not necessary to use a .def file.

 

Exporting from a DLL Using __declspec(dllexport)

 

A .DLL file has a layout very similar to an .EXE file, with one important difference: a DLL file contains an exports table. The exports table contains the name of every function that the DLL exports to other executables. These functions are the entry points into the DLL; only the functions in the exports table can be accessed by other executables. Any other functions in the DLL are private to the DLL. The exports table of a DLL can be viewed by using the DUMPBIN tool with the /EXPORTS option. You can export functions from a DLL using two methods:

  1. Create a module definition (.DEF) file and use the .DEF file when building the DLL. Use this approach if you want to export functions from your DLL by ordinal rather than by name.

  2. Use the keyword __declspec(dllexport) in the function's definition.

 

When exporting functions with either method, make sure to use the __stdcall calling convention. Microsoft introduced __export in the 16-bit compiler version of Visual C++ to allow the compiler to generate the export names automatically and place them in a .LIB file. This .LIB file could then be used just like a static .LIB to link with a DLL.

In the 32-bit compiler version, you can export data, functions, classes, or class member functions from a DLL using the __declspec(dllexport) keyword. __declspec(dllexport) adds the export directive to the object file so you don't need to use a .DEF file.

This convenience is most apparent when trying to export decorated C++ function names. There is no standard specification for name decoration, so the name of an exported function may change between compiler versions. If you use __declspec(dllexport), recompiling the DLL and dependent .EXE files is necessary only to account for any naming convention changes.

Many export directives, such as ordinals, NONAME, and PRIVATE, can be made only in a .DEF file, and there is no way to specify these attributes without a .DEF file. However, using __declspec(dllexport) in addition to using a .DEF file does not cause build errors. To export functions, the __declspec(dllexport) keyword must appear to the left of the calling-convention keyword, if a keyword is specified. For example:

__declspec(dllexport) void __cdecl FunctionName(void);

To export all of the public data members and member functions in a class, the keyword must appear to the left of the class name as follows:

class __declspec(dllexport) CExampleExport : public CObject

{ ... class definition ... };

When building your DLL, you typically create a header file that contains the function prototypes and/or classes you are exporting, and add __declspec(dllexport) to the declarations in the header file. To make your code more readable, define a macro for __declspec(dllexport) and use the macro with each symbol you are exporting. For example:

#define DllExport   __declspec(dllexport) 

__declspec(dllexport) stores function names in the DLL's export table. When porting DLL source code from Win16 to Win32, replace each instance of __export with __declspec(dllexport). As a reference, search through the Win32 WINBASE.H header file for examples of __declspec(dllimport) usage.

 

Exporting C Functions for Use in C or C++ Language Executables

 

If you have functions in a DLL written in C that you want to access from a C-language or C++-language module, you should use the __cplusplus preprocessor macro to determine which language is being compiled, and then declare these functions with C linkage if being used from a C++-language module. If you use this technique and provide header files for your DLL, these functions can be used by C and C++ users with no change.

The following code shows a header file which can be used by C and C++ client applications:

// MyCFuncs.h

#ifdef __cplusplus

extern "C" {  // only need to export C interface if used by C++ source code

#endif

 

__declspec(dllimport) void MyCFunc();

__declspec(dllimport) void AnotherCFunc();

 

#ifdef __cplusplus

}

#endif

If you need to link C functions to your C++ executable and the function declaration header files have not used the above technique, in the C++ source file, do the following to prevent the compiler from decorating the C function names:

extern "C" {

#include "MyCHeader.h"

}

Creating an Import Library

 

An import library (.lib) file contains information the linker needs to resolve external references to exported DLL functions, so the system can locate the specified DLL and exported DLL functions at run time. For example, to call the CreateWindow() function, you must link your code with the import library User32.lib. The reason is that CreateWindow() resides in a system DLL named User32.dll, and User32.lib is the import library used to resolve the calls to exported functions in User32.lib in your code. The linker creates a table that contains the address of each function call. Calls to functions in a DLL will be fixed up when the DLL is loaded. While the system is initializing the process, it loads User32.dll because the process depends on exported functions in that DLL, and it updates the entries in the function address table. All calls to CreateWindow() invoke the function exported from User32.dll. Warning:  Calling the ExitProcess() function in a DLL can lead to unexpected application or system errors. Be sure to call ExitProcess() from a DLL only if you know which applications or system components will load the DLL and that it is safe to call ExitProcess() in this context.

 

Dynamic-Link Library Updates

 

It is sometimes necessary to replace a DLL with a newer version. Before replacing a DLL, perform a version check to ensure that you are replacing an older version with a newer version. It is possible to replace a DLL that is in use. The method you use to replace DLLs that are in use depends on the operating system you are using. On Windows XP and later, applications should use Isolated Applications and Side-by-side Assemblies. It is not necessary to restart the computer if you perform the following steps:

 

  1. Use the MoveFileEx() function to rename the DLL being replaced. Do not specify MOVEFILE_COPY_ALLOWED, and make sure the renamed file is on the same volume that contains the original file. You could also simply rename the file in the same directory by giving it a different extension.

  2. Copy the new DLL to the directory that contains the renamed DLL. All applications will now use the new DLL.

  3. Use MoveFileEx() with MOVEFILE_DELAY_UNTIL_REBOOT to delete the renamed DLL.

 

Before you make this replacement, applications will use the original DLL until it is unloaded. After you make the replacement, applications will use the new DLL. When you write a DLL, you must be careful to ensure that it is prepared for this situation, especially if the DLL maintains global state information or communicates with other services. If the DLL is not prepared for a change in global state information or communication protocols, updating the DLL will require you to restart the computer to ensure that all applications are using the same version of the DLL. For Windows Me/98/95:  Because MoveFileEx() is not supported, it is necessary to restart the computer.

 

Dynamic-Link Library Redirection

 

Problems can occur when an application loads a version of a DLL other than the one with which it shipped. Starting with Windows 2000, you can ensure that your application uses the correct version of a DLL by creating a redirection file. The contents of a redirection file are ignored, but its presence forces all DLLs in the application's directory to be loaded from that directory.

The redirection file must be named as follows: appname.local.

For example, if the application's name is editor.exe, the redirection file is named editor.exe.local. You must install editor.exe.local in the same directory that contains editor.exe. You must also install the DLLs in the same directory. The LoadLibrary() and LoadLibraryEx() functions change their search sequence if a redirection file is present. If a path is specified and there is a redirection file for the application, these functions search for the DLL in the application's directory. If the DLL exists in the application's directory, these functions ignore the specified path and load the DLL from the application's directory. If the module is not in the application's directory, these functions load the DLL from the specified directory. For example, an application c:\myapp\myapp.exe calls LoadLibrary() using the following path:

c:\program files\common files\system\mydll.dll

If c:\myapp\myapp.exe.local and c:\myapp\mydll.dll exist, LoadLibrary() will load c:\myapp\mydll.dll. Otherwise, LoadLibrary() will load c:\program files\common files\system\mydll.dll. It is good practice to install your application's DLLs in the same directory that contains the application, even if you are not using redirection. It ensures that installing your application will not overwrite other copies of the DLL and cause other applications to fail. In addition, other applications will not overwrite your copy of the DLL and cause your application to fail.

 

Dynamic-Link Library Data

 

A Dynamic-Link Library (DLL) can contain global data or local data.

 

Variable Scope

 

The default scope of DLL variables is the same as that of variables declared in the application. Global variables in a DLL source code file are global to each process using the DLL. Static variables have scope limited to the block in which they are declared. As a result, each process has its own instance of the DLL global and static variables by default. Your development tools may allow you to override the default scope of global and static variables.

 

Dynamic Memory Allocation

 

When a DLL allocates memory using any of the memory allocation functions (GlobalAlloc(), LocalAlloc(), HeapAlloc(), and VirtualAlloc()), the memory is allocated in the virtual address space of the calling process and is accessible only to the threads of that process. A DLL can use file mapping to allocate memory that can be shared among processes.

 

Thread Local Storage

 

The thread local storage (TLS) functions enable a DLL to allocate an index for storing and retrieving a different value for each thread of a multithreaded process. For example, a spreadsheet application can create a new instance of the same thread each time the user opens a new spreadsheet. A DLL providing the functions for various spreadsheet operations can use TLS to save information about the current state of each spreadsheet (row, column, and so on).

Warning:  The Visual C++ compiler supports a syntax that enables you to declare thread-local variables: _declspec(thread). If you use this syntax in a DLL, you will not be able to load the DLL explicitly using LoadLibrary() or LoadLibraryEx(). If your DLL will be loaded explicitly, you must use the thread local storage functions instead of _declspec(thread).

 

 

 

 

 

 

 

 

 

 

 

 

 

Further reading and digging:

 

  1. Structure, enum, union and typedef story can be found C/C++ struct, enum, union & typedef.

  2. For Multibytes, Unicode characters and Localization please refer to Locale, wide characters & Unicode (Story) and Windows users & groups programming tutorials (Implementation).

  3. Windows data types are Windows data types.

  4. Microsoft Visual C++, online MSDN.

  5. Check the best selling C / C++ and Windows books at Amazon.com.

 

 

 

 

 

 

|< Dynamic Link Library, DLL 1 | Main | Dynamic Link Library, DLL 3 >| Site Index | Download |