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:
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:
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.
|
// 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:
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: |
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:
The order in which function arguments are pushed onto the stack.
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.
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 */
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:
Uses the TlsAlloc() function to allocate a TLS index.
Allocates a block of memory to be used exclusively by the initial thread of the process.
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.
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:
Well, our DLL program looks OK. That all folks. Enjoy your C / C++ journey!
The following elements are used in dynamic linking.
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 LoadModule() function is provided only for compatibility with 16-bit versions of Windows.
Further reading and digging:
Structure, enum, union and typedef story can be foundC/C++ struct, enum, union & typedef.
For Multi bytes, Unicode characters and Localization please refer to Locale, wide characters & Unicode (Story) and Windows users & groups programming tutorials (Implementation).
Windows data types areWindows data types.
Microsoft Visual C++, online MSDN.
Check the best selling C / C++ and Windows books at Amazon.com.