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


 

 

 

MODULE CC2

DYNAMIC LINK LIBRARY - DLL

Part 4: PROGRAM EXAMPLES

 

 

 

What do we have in this Module?

  1. Using Shared Memory in a Dynamic-Link Library

  2. Testing Our MainDll()

  3. Function Calling Convention

  4. Using Thread Local Storage in a Dynamic-Link Library

  5. Testing the DLL Program

  6. Dynamic-Link Library Reference

  7. Functions

  8. Obsolete Functions

 

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

 

 

 

 

 

 

 

 

 

The Win32 programming skills:

  • Able to understand, build and run the dynamic-link library programs.

  • Able to differentiate between the static and dynamic linking.

  • Able to recognize different types of the DLL.

  • Able to create exported and imported functions of DLL.

Using Shared Memory in a Dynamic-Link Library

 

This section shows how the DLL entry-point function can use a file-mapping object to set up memory that can be shared by processes that load the DLL. The shared DLL memory persists only as long as the DLL is loaded. The example uses file mapping to map a block of named shared memory into the virtual address space of each process that loads the DLL. To do this, the entry-point function must:

 

  1. Call the CreateFileMapping() function to get a handle to a file-mapping object. The first process that loads the DLL creates the file-mapping object. Subsequent processes open a handle to the existing object.

  2. Call the MapViewOfFile() function to map a view into the virtual address space. This enables the process to access the shared memory.

 

Note that while you can specify default security attributes by passing in a NULL value for the lpAttributesparameter of CreateFileMapping(), you may choose to use a SECURITY_ATTRIBUTES structure to provide additional security.

This is an empty DLL project (program), you need to set your project to use __stdcall (using WINAPI) convention for dllmain(). The following is the setting for Visual C++ .Net.

 

Setting the __stdcall (using WINAPI) function calling convention.

 

 

// Project name: moredll, File name:  dllentryfunc.cpp generating moredll.dll

// but no moredll.lib! The DLL entry-point function sets up shared memory using

// a named file-mapping object.

#include <windows.h>

#include <stdio.h>

#include <memory.h>

 

#define SHMEMSIZE 4096

 

static LPVOID lpvMem = NULL;      // pointer to shared memory

static HANDLE hMapObject = NULL;  // handle to file mapping

 

BOOL DllMain(HINSTANCE hinstDLL,  // DLL module handle

    DWORD fdwReason,             // reason called

    LPVOID lpvReserved)          // reserved

{

    BOOL fInit, fIgnore;

   switch (fdwReason)

    {

       // The DLL is loading due to process

       // initialization or a call to LoadLibrary.

       case DLL_PROCESS_ATTACH:

                printf("The DLL is loading...from moredll.dll.\n");

           // Create a named file mapping object.

             hMapObject = CreateFileMapping(

                INVALID_HANDLE_VALUE, // use paging file

                NULL,                                       // default security attributes

                PAGE_READWRITE,             // read/write access

                0,                                               // size: high 32-bits

                SHMEMSIZE,                          // size: low 32-bits

                "dllmemfilemap");                     // name of map object

           if (hMapObject == NULL)

return FALSE;

           else

              printf("CreateFileMapping() is OK.\n");

            // The first process to attach initializes memory.

             fInit = (GetLastError() != ERROR_ALREADY_EXISTS);

           // Get a pointer to the file-mapped shared memory.

            lpvMem = MapViewOfFile(

                hMapObject,              // object to map view of

                FILE_MAP_WRITE, // read/write access

                0,                               // high offset:  map from

                0,                               // low offset:   beginning

                0);                              // default: map entire file

 

   if (lpvMem == NULL)

return FALSE;

            else

printf("MapViewOfFile() is OK.\n");

           // Initialize memory if this is the first process.

           if (fInit)

                memset(lpvMem, '\0', SHMEMSIZE);

           break;

       // The attached process creates a new thread.

       case DLL_THREAD_ATTACH:

printf("The attached process creates a new thread...from moredll.dll.\n");

           break;

       // The thread of the attached process terminates.

       case DLL_THREAD_DETACH:

printf("The thread of the attached process terminates... from moredll.dll.\n");

           break;

// The DLL is unloading from a process due to

// process termination or a call to FreeLibrary().

       case DLL_PROCESS_DETACH:

              printf("The DLL is unloading from a process... from moredll.dll.\n");

           // Unmap shared memory from the process's address space.

            fIgnore = UnmapViewOfFile(lpvMem);

           // Close the process's handle to the file-mapping object.

            fIgnore = CloseHandle(hMapObject);

             break;

       default:

printf("Reason called not matched, error if any: %d... from moredll.dll.\n", GetLastError());

         break;

     }

   return TRUE;

    UNREFERENCED_PARAMETER(hinstDLL);

    UNREFERENCED_PARAMETER(lpvReserved);

}

 

// Can be commented out for this example...

// SetSharedMem() sets the contents of shared memory.

VOID SetSharedMem(LPTSTR lpszBuf)

{

    LPTSTR lpszTmp = "Testing some string";

   // Get the address of the shared memory block.

    lpszTmp = (LPTSTR) lpvMem;

   // Copy the null-terminated string into shared memory.

   while (*lpszBuf)

        *lpszTmp++ = *lpszBuf++;

    *lpszTmp = '\0';

printf("The content: %s.\n", lpszTmp);

}

 

// Can be commented out for this example...

// GetSharedMem() gets the contents of shared memory.

VOID GetSharedMem(LPTSTR lpszBuf, DWORD cchSize)

{

    LPTSTR lpszTmp;

    // Get the address of the shared memory block.

    lpszTmp = (LPTSTR) lpvMem;

   // Copy from shared memory into the caller's buffer.

   while (*lpszTmp && --cchSize)

        *lpszBuf++ = *lpszTmp++;

    *lpszBuf = '\0';

printf("The caller buffer: %s.\n", lpszBuf);

}

 

A sample output:

 

Run-time dynamic linking: generating the DLL file using MainDll()

 

Change your project to the Release version and rebuild the DLL program.  If there is no error, copies the DLL file (moredll.dll in this example) to Windows system directory. We will test the DLL file in next section.

 

Testing Our MainDll()

 

Let test the generated moredll.dll by executing the following simple program.  There is no function to call (export) here. The moredll.dll has been copied to C:\\WINDOWS\System32 directory (Windows Xp Pro).

 

// File:  testdll.cpp, using moredll.dll that uses Dllmain()

// Using Run-Time Dynamic Linking

// A simple program that uses LoadLibrary() and

// GetProcAddress() to access Dllmain() of moredll.dll.

// No function to be exported, just testing...

#include <stdio.h>

#include <windows.h>

 

typedefvoid (*MYPROC)(LPTSTR);

 

int main()

{

    HINSTANCE hinstLib;

    MYPROC ProcAdd;

    BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;

 

   // Get a handle to our DLL module, moredll.dll...this module has been copied

    // to C:\WINDOWS\System32 directory...

    hinstLib = LoadLibrary("moredll");

   // If the handle is valid, try to get the function address.

   if (hinstLib != NULL)

    {

              printf("The DLL handle is valid...\n");

              ProcAdd = (MYPROC) GetProcAddress(hinstLib, "Anonymfunction");

       // If the function address is valid, call the function.

       if (ProcAdd != NULL)

        {

              printf("The function address is valid...\n\n");

              fRunTimeLinkSuccess = TRUE;

             // Ready to execute DLLmain()...

        }

      else

              printf("The function address is not valid, error: %d.\n", GetLastError());

       }

      else

              printf("\nThe DLL handle is NOT valid, error: %d\n", GetLastError());

// Free the DLL module.

fFreeResult = FreeLibrary(hinstLib);

              if (fFreeResult != 0)

                      printf("FreeLibrary() is OK.\n");

              else

                      printf("FreeLibrary() is not OK.\n");

 return 0;

}

 

A sample output:

 

Run-time dynamic linking: testing the MainDll() program example output

 

error: 127 -The specified procedure could not be found. (ERROR_PROC_NOT_FOUND) should be expected because we did not define any function in our DLL program.Note that the shared memory can be mapped to a different address in each process. For this reason, each process has its own instance of the lpvMem parameter, which is declared as a global variable so that it is available to all DLL functions. The example assumes that the DLL global data is not shared, so each process that loads the DLL has its own instance of lpvMem.

In this example, the shared memory is released when the last handle to the file-mapping object is closed. To create persistent shared memory, a DLL can create a detached process when the DLL is first loaded. If this detached process uses the DLL and does not terminate, it has a handle to the file-mapping object that prevents the shared memory from being released.

 

Function Calling Convention

 

For every function call there will be a creation of a stack frame.  It is very useful if we can study the operation of the function call and how the stack frame for function is constructed and destroyed.  For function call, compilers have some convention used for calling them.  A convention is a way of doing things that is standardized, but not a documented standard. For example, the C/C++ function calling convention tells the compiler things such as:

  1. The order in which function arguments are pushed onto the stack.

  2. Whether the caller function or called function (callee) responsibility to remove the arguments from the stack at the end of the call that is the stack cleanup process.

  3. The name-decorating convention that the compiler uses to identify individual functions.

 

Examples for calling conventions are __stdcall,__pascal, __cdecl and __fastcall (for Microsoft Visual C++).  The calling convention belongs to a function's signature, thus functions with different calling convention are incompatible with each other.  There is currently no standard for C/C++ naming between compiler vendors or even between different versions of a compiler for function calling scheme. That is why if you link object files compiled with other compilers may not produce the same naming scheme and thus causes unresolved externals.  For Borland and Microsoft compilers you specify explicitly a specific calling convention between the return type and the function's name as shown below.

void __cdecl TestFunc(float a, char b, char c);   // Borland and Microsoft

Or as shown in the previous examples, you can do this through the Visual C++/.Net setting. For the GNU GCC you use the __attribute__ keyword by writing the function definition followed by the keyword__attribute__ and then state the calling convention in double parentheses as shown below.

void  TestFunc(float a, char b, char c)  __attribute__((cdecl));  // GNU GCC

As an example, Microsoft Visual C++ compiler has three function calling conventions used as listed in the following table.

 

keyword

Stack cleanup

Parameter passing

__cdecl

caller

Pushes parameters on the stack, in reverse order (right to left).  Caller cleans up the stack.  This is the default calling convention for C language that supports variadic functions (variable number of argument or type list such as printf()) and also C++ programs.  The cdecl calling convention creates larger executables than __stdcall, because it requires each function call to include stack cleanup code.

__stdcall

callee

Also known as __pascal.  Pushes parameters on the stack, in reverse order (right to left).  Functions that use this calling convention require a function prototype.  Callee cleans up the stack.  It is standard convention used in Win32 API functions.

__fastcall

callee

Parameters stored in registers, then pushed on stack.  The __fastcall calling convention specifies that arguments to functions are to be passed in registers, when possible.  Callee cleans up the stack.

 

Table 1

 

Basically, C function calls are made with the caller pushing some parameters onto the stack, calling the function and then popping the stack to clean up those pushed arguments.  For __cdecl in assembly language example is shown below:

/*example of __cdecl*/

push arg1

push arg2

call function

add ebp, 12   ;stack cleanup

And for __stdcall example:

/*example of __stdcall*/

push arg1

push arg2

call function

/* No stack cleanup, it will be done by caller */

Using Thread Local Storage in a Dynamic-Link Library

 

This section shows the use of a DLL entry-point function to set up a thread local storage (TLS) index to provide private storage for each thread of a multithreaded process.  The entry-point function uses the TlsAlloc() function to allocate a TLS index whenever a process loads the DLL. Each thread can then use this index to store a pointer to its own block of memory.  When the entry-point function is called with the DLL_PROCESS_ATTACH value, the code performs the following actions:

  1. Uses the TlsAlloc() function to allocate a TLS index.

  2. Allocates a block of memory to be used exclusively by the initial thread of the process.

  3. Uses the TLS index in a call to the TlsSetValue() function to store a pointer to the allocated memory.

 

Each time the process creates a new thread, the entry-point function is called with the DLL_THREAD_ATTACH value. The entry-point function then allocates a block of memory for the new thread and stores a pointer to it by using the TLS index. Each thread can use the TLS index in a call toTlsGetValue() to retrieve the pointer to its own block of memory.

When a thread terminates, the entry-point function is called with theDLL_THREAD_DETACH value and the memory for that thread is freed. When a process terminates, the entry-point function is called with the DLL_PROCESS_DETACH value and the memory referenced by the pointer in the TLS index is freed.

The TLS index is stored in a global variable, making it available to all of the DLL functions. The following example assumes that the DLL's global data is not shared, because the TLS index is not necessarily the same for each process that loads the DLL. This is an empty DLL project (program).

 

// Project name: moredll, File name: dllntls.cpp, generating moredll.dll

// Using Thread Local Storage in a Dynamic Link Library

#include <windows.h>

#include <stdio.h>

 

static DWORD dwTlsIndex; // address of shared memory

 

// DllMain() is the entry-point function for this DLL.

BOOL DllMain(HINSTANCE hinstDLL,  // DLL module handle

    DWORD fdwReason,             // reason called

    LPVOID lpvReserved)            // reserved

 

{

    LPVOID lpvData;

    BOOL fIgnore;

 

   switch (fdwReason)

    {

       // The DLL is loading due to process

       // initialization or a call to LoadLibrary.

       case DLL_PROCESS_ATTACH:

              printf("Loading the DLL...\n");

           // Allocate a TLS index.

           if ((dwTlsIndex = TlsAlloc()) == 0xFFFFFFFF)

               return FALSE;

               // No break: Initialize the index for first thread.

       // The attached process creates a new thread.

       case DLL_THREAD_ATTACH:

            printf("The attached process creating a new thread...\n");

           // Initialize the TLS index for this thread.

            lpvData = (LPVOID) LocalAlloc(LPTR, 256);

           if (lpvData != NULL)

                fIgnore = TlsSetValue(dwTlsIndex, lpvData);

           break;

       // The thread of the attached process terminates.

       case DLL_THREAD_DETACH:

            printf("The thread of the attached process terminates...\n");

            // Release the allocated memory for this thread.

            lpvData = TlsGetValue(dwTlsIndex);

           if (lpvData != NULL)

                LocalFree((HLOCAL) lpvData);

           break;

       // DLL unload due to process termination or FreeLibrary.

       case DLL_PROCESS_DETACH:

            printf("DLL unloading...\n");

           // Release the allocated memory for this thread.

            lpvData = TlsGetValue(dwTlsIndex);

           if (lpvData != NULL)

                LocalFree((HLOCAL) lpvData);

           // Release the TLS index.

            TlsFree(dwTlsIndex);

           break;

       default:

              printf("Reason called not matched, error if any: %d...\n", GetLastError());

           break;

    }

   return TRUE;

    UNREFERENCED_PARAMETER(hinstDLL);

    UNREFERENCED_PARAMETER(lpvReserved);

}

 

When a process uses load-time linking with this DLL, the entry-point function is sufficient to manage the thread local storage. Problems can occur with a process that uses run-time linking because the entry-point function is not called for threads that exist before the LoadLibrary() function is called, so TLS memory is not allocated for these threads. The following example solves this problem by checking the value returned by theTlsGetValue() function and allocating memory if the value indicates that the TLS slot for this thread is not set.

LPVOID lpvData;

// Retrieve a data pointer for the current thread.

lpvData = TlsGetValue(dwTlsIndex);

 

// If NULL, allocate memory for this thread.

if (lpvData == NULL)

{

    lpvData = (LPVOID) LocalAlloc(LPTR, 256);

    if (lpvData != NULL)

        TlsSetValue(dwTlsIndex, lpvData);

}

Change your project to the Release mode and rebuild the DLL program.  If there is no error, copies the DLL file (moredll.dll in this example) to Windows system directory. We will test the DLL file in next section.

 

Testing the DLL Program

 

By using the previous test program example, let test the DLL program.

 

// File:  testdll.cpp, using moredll.dll that uses Dllmain()

// Using Run-Time Dynamic Linking

// A simple program that uses LoadLibrary() and

// GetProcAddress() to access Dllmain() of moredll.dll.

// No function to be exported/imported, just testing...

#include <stdio.h>

#include <windows.h>

 

typedefvoid (*MYPROC)(LPTSTR);

 

int main()

{

    HINSTANCE hinstLib;

    MYPROC ProcAdd;

    BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;

   // Get a handle to our DLL module, moredll.dll...this module has been copied

    // to C:\WINDOWS\System32 directory...

    hinstLib = LoadLibrary("moredll");

   // If the handle is valid, try to get the function address.

   if (hinstLib != NULL)

    {

              printf("The DLL handle is valid...\n");

              ProcAdd = (MYPROC) GetProcAddress(hinstLib, "Anonymfunction");

       // If the function address is valid, call the function.

       if (ProcAdd != NULL)

        {

           printf("The function address is valid...\n\n");

           fRunTimeLinkSuccess = TRUE;

          // Ready to execute DllMain()...

        }

      else

              printf("The function address is not valid, error: %d.\n", GetLastError());

       }

      else

              printf("\nThe DLL handle is NOT valid, error: %d\n", GetLastError());

        // Free the DLL module.

       fFreeResult = FreeLibrary(hinstLib);

       if (fFreeResult != 0)

           printf("FreeLibrary() is OK.\n");

       else

           printf("FreeLibrary() is not OK.\n");

 return 0;

}

 

A sample output:

 

Windows DLL program examples using Thread Local Storage

 

Well, our DLL program looks OK. That all folks. Enjoy your C / C++ journey!

 

Dynamic-Link Library Reference

 

The following elements are used in dynamic linking.

 

Functions

 

The following functions are used in dynamic linking.

 

Function

Description

DisableThreadLibraryCalls()

Disables thread attach and thread detach notifications for the specified DLL.

DllMain()

An optional entry point into a DLL.

FreeLibrary()

Decrements the reference count of the loaded DLL. When the reference count reaches zero, the module is unmapped from the address space of the calling process.

FreeLibraryAndExitThread()

Decrements the reference count of a loaded DLL by one, and then calls ExitThread() to terminate the calling thread.

GetDllDirectory()

Retrieves the application-specific portion of the search path used to locate DLLs for the application.

GetModuleFileName()

Retrieves the fully-qualified path for the file containing the specified module.

GetModuleFileNameEx()

Retrieves the fully-qualified path for the file containing the specified module.

GetModuleHandle()

Retrieves a module handle for the specified module.

GetModuleHandleEx()

Retrieves a module handle for the specified module.

GetProcAddress()

Retrieves the address of an exported function or variable from the specified DLL.

LoadLibrary()

Maps the specified executable module into the address space of the calling process.

LoadLibraryEx()

Maps the specified executable module into the address space of the calling process.

SetDllDirectory()

Modifies the search path used to locate DLLs for the application.

 

Table 2

 

The Obsolete Functions

 

The LoadModule() function is provided only for compatibility with 16-bit versions of Windows.

 

 

 

 

 

 

 

Further reading and digging:

 

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

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

  3. Windows data types areWindows 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 3 |Main |Windows Services 1 >|Site Index |Download |