Redigera

Dela via


DLLs and Visual C++ run-time library behavior

When you build a Dynamic-link Library (DLL) by using Visual Studio, by default, the linker includes the Visual C++ run-time library (VCRuntime). The VCRuntime contains code required to initialize and terminate a C/C++ executable. When linked into a DLL, the VCRuntime code provides an internal DLL entry-point function called _DllMainCRTStartup that handles Windows OS messages to the DLL to attach to or detach from a process or thread. The _DllMainCRTStartup function performs essential tasks such as stack buffer security set up, C run-time library (CRT) initialization and termination, and calls to constructors and destructors for static and global objects. _DllMainCRTStartup also calls hook functions for other libraries such as WinRT, MFC, and ATL to perform their own initialization and termination. Without this initialization, the CRT and other libraries, as well as your static variables, would be left in an uninitialized state. The same VCRuntime internal initialization and termination routines are called whether your DLL uses a statically linked CRT or a dynamically linked CRT DLL.

Default DLL entry point _DllMainCRTStartup

In Windows, all DLLs can contain an optional entry-point function, usually called DllMain, that is called for both initialization and termination. This gives you an opportunity to allocate or release additional resources as needed. Windows calls the entry-point function in four situations: process attach, process detach, thread attach, and thread detach. When a DLL is loaded into a process address space, either when an application that uses it is loaded, or when the application requests the DLL at runtime, the operating system creates a separate copy of the DLL data. This is called process attach. Thread attach occurs when the process the DLL is loaded in creates a new thread. Thread detach occurs when the thread terminates, and process detach is when the DLL is no longer required and is released by an application. The operating system makes a separate call to the DLL entry point for each of these events, passing a reason argument for each event type. For example, the OS sends DLL_PROCESS_ATTACH as the reason argument to signal process attach.

The VCRuntime library provides an entry-point function called _DllMainCRTStartup to handle default initialization and termination operations. On process attach, the _DllMainCRTStartup function sets up buffer security checks, initializes the CRT and other libraries, initializes run-time type information, initializes and calls constructors for static and non-local data, initializes thread-local storage, increments an internal static counter for each attach, and then calls a user- or library-supplied DllMain. On process detach, the function goes through these steps in reverse. It calls DllMain, decrements the internal counter, calls destructors, calls CRT termination functions and registered atexit functions, and notifies any other libraries of termination. When the attachment counter goes to zero, the function returns FALSE to indicate to Windows that the DLL can be unloaded. The _DllMainCRTStartup function is also called during thread attach and thread detach. In these cases, the VCRuntime code does no additional initialization or termination on its own, and just calls DllMain to pass the message along. If DllMain returns FALSE from process attach, signaling failure, _DllMainCRTStartup calls DllMain again and passes DLL_PROCESS_DETACH as the reason argument, then goes through the rest of the termination process.

When building DLLs in Visual Studio, the default entry point _DllMainCRTStartup supplied by VCRuntime is linked in automatically. You do not need to specify an entry-point function for your DLL by using the /ENTRY (Entry point symbol) linker option.

Note

While it is possible to specify another entry-point function for a DLL by using the /ENTRY: linker option, we do not recommend it, because your entry-point function would have to duplicate everything that _DllMainCRTStartup does, in the same order. The VCRuntime provides functions that allow you to duplicate its behavior. For example, you can call __security_init_cookie immediately on process attach to support the /GS (Buffer security check) buffer checking option. You can call the _CRT_INIT function, passing the same parameters as the entry point function, to perform the rest of the DLL initialization or termination functions.

Initialize a DLL

Your DLL may have initialization code that must execute when your DLL loads. In order for you to perform your own DLL initialization and termination functions, _DllMainCRTStartup calls a function called DllMain that you can provide. Your DllMain must have the signature required for a DLL entry point. The default entry point function _DllMainCRTStartup calls DllMain using the same parameters passed by Windows. By default, if you do not provide a DllMain function, Visual Studio provides one for you and links it in so that _DllMainCRTStartup always has something to call. This means that if you do not need to initialize your DLL, there is nothing special you have to do when building your DLL.

This is the signature used for DllMain:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved); // reserved

Some libraries wrap the DllMain function for you. For example, in a regular MFC DLL, implement the CWinApp object's InitInstance and ExitInstance member functions to perform initialization and termination required by your DLL. For more details, see the Initialize regular MFC DLLs section.

Warning

There are significant limits on what you can safely do in a DLL entry point. For more information about specific Windows APIs that are unsafe to call in DllMain, see General Best Practices. If you need anything but the simplest initialization then do that in an initialization function for the DLL. You can require applications to call the initialization function after DllMain has run and before they call any other functions in the DLL.

Initialize ordinary (non-MFC) DLLs

To perform your own initialization in ordinary (non-MFC) DLLs that use the VCRuntime-supplied _DllMainCRTStartup entry point, your DLL source code must contain a function called DllMain. The following code presents a basic skeleton showing what the definition of DllMain might look like:

#include <windows.h>

extern "C" BOOL WINAPI DllMain (
    HINSTANCE const instance,  // handle to DLL module
    DWORD     const reason,    // reason for calling function
    LPVOID    const reserved)  // reserved
{
    // Perform actions based on the reason for calling.
    switch (reason)
    {
    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;
    }
    return TRUE;  // Successful DLL_PROCESS_ATTACH.
}

Note

Older Windows SDK documentation says that the actual name of the DLL entry-point function must be specified on the linker command-line with the /ENTRY option. With Visual Studio, you do not need to use the /ENTRY option if the name of your entry-point function is DllMain. In fact, if you use the /ENTRY option and name your entry-point function something other than DllMain, the CRT does not get initialized properly unless your entry-point function makes the same initialization calls that _DllMainCRTStartup makes.

Initialize regular MFC DLLs

Because regular MFC DLLs have a CWinApp object, they should perform their initialization and termination tasks in the same location as an MFC application: in the InitInstance and ExitInstance member functions of the DLL's CWinApp-derived class. Because MFC provides a DllMain function that is called by _DllMainCRTStartup for DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH, you should not write your own DllMain function. The MFC-provided DllMain function calls InitInstance when your DLL is loaded and it calls ExitInstance before the DLL is unloaded.

A regular MFC DLL can keep track of multiple threads by calling TlsAlloc and TlsGetValue in its InitInstance function. These functions allow the DLL to track thread-specific data.

In your regular MFC DLL that dynamically links to MFC, if you are using any MFC OLE, MFC Database (or DAO), or MFC Sockets support, respectively, the debug MFC extension DLLs MFCOversionD.dll, MFCDversionD.dll, and MFCNversionD.dll (where version is the version number) are linked in automatically. You must call one of the following predefined initialization functions for each of these DLLs that you are using in your regular MFC DLL's CWinApp::InitInstance.

Type of MFC support Initialization function to call
MFC OLE (MFCOversionD.dll) AfxOleInitModule
MFC Database (MFCDversionD.dll) AfxDbInitModule
MFC Sockets (MFCNversionD.dll) AfxNetInitModule

Initialize MFC extension DLLs

Because MFC extension DLLs do not have a CWinApp-derived object (as do regular MFC DLLs), you should add your initialization and termination code to the DllMain function that the MFC DLL Wizard generates.

The wizard provides the following code for MFC extension DLLs. In the code, PROJNAME is a placeholder for the name of your project.

#include "pch.h" // For Visual Studio 2017 and earlier, use "stdafx.h"
#include <afxdllx.h>

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
static AFX_EXTENSION_MODULE PROJNAMEDLL;

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      TRACE0("PROJNAME.DLL Initializing!\n");

      // MFC extension DLL one-time initialization
      AfxInitExtensionModule(PROJNAMEDLL,
                                 hInstance);

      // Insert this DLL into the resource chain
      new CDynLinkLibrary(Dll3DLL);
   }
   else if (dwReason == DLL_PROCESS_DETACH)
   {
      TRACE0("PROJNAME.DLL Terminating!\n");
   }
   return 1;   // ok
}

Creating a new CDynLinkLibrary object during initialization allows the MFC extension DLL to export CRuntimeClass objects or resources to the client application.

If you are going to use your MFC extension DLL from one or more regular MFC DLLs, you must export an initialization function that creates a CDynLinkLibrary object. That function must be called from each of the regular MFC DLLs that use the MFC extension DLL. An appropriate place to call this initialization function is in the InitInstance member function of the regular MFC DLL's CWinApp-derived object before using any of the MFC extension DLL's exported classes or functions.

In the DllMain that the MFC DLL Wizard generates, the call to AfxInitExtensionModule captures the module's run-time classes (CRuntimeClass structures) as well as its object factories (COleObjectFactory objects) for use when the CDynLinkLibrary object is created. You should check the return value of AfxInitExtensionModule; if a zero value is returned from AfxInitExtensionModule, return zero from your DllMain function.

If your MFC extension DLL will be explicitly linked to an executable (meaning the executable calls AfxLoadLibrary to link to the DLL), you should add a call to AfxTermExtensionModule on DLL_PROCESS_DETACH. This function allows MFC to clean up the MFC extension DLL when each process detaches from the MFC extension DLL (which happens when the process exits or when the DLL is unloaded as a result of a AfxFreeLibrary call). If your MFC extension DLL will be linked implicitly to the application, the call to AfxTermExtensionModule is not necessary.

Applications that explicitly link to MFC extension DLLs must call AfxTermExtensionModule when freeing the DLL. They should also use AfxLoadLibrary and AfxFreeLibrary (instead of the Win32 functions LoadLibrary and FreeLibrary) if the application uses multiple threads. Using AfxLoadLibrary and AfxFreeLibrary ensures that the startup and shutdown code that executes when the MFC extension DLL is loaded and unloaded does not corrupt the global MFC state.

Because the MFCx0.dll is fully initialized by the time DllMain is called, you can allocate memory and call MFC functions within DllMain (unlike the 16-bit version of MFC).

Extension DLLs can take care of multithreading by handling the DLL_THREAD_ATTACH and DLL_THREAD_DETACH cases in the DllMain function. These cases are passed to DllMain when threads attach and detach from the DLL. Calling TlsAlloc when a DLL is attaching allows the DLL to maintain thread local storage (TLS) indexes for every thread attached to the DLL.

Note that the header file Afxdllx.h contains special definitions for structures used in MFC extension DLLs, such as the definition for AFX_EXTENSION_MODULE and CDynLinkLibrary. You should include this header file in your MFC extension DLL.

Note

It is important that you neither define nor undefine any of the _AFX_NO_XXX macros in pch.h (stdafx.h in Visual Studio 2017 and earlier). These macros exist only for the purpose of checking whether a particular target platform supports that feature or not. You can write your program to check these macros (for example, #ifndef _AFX_NO_OLE_SUPPORT), but your program should never define or undefine these macros.

A sample initialization function that handles multithreading is included in Using Thread Local Storage in a Dynamic-Link Library in the Windows SDK. Note that the sample contains an entry-point function called LibMain, but you should name this function DllMain so that it works with the MFC and C run-time libraries.

See also

Create C/C++ DLLs in Visual Studio
DllMain entry point
Dynamic-link Library Best Practices