原文地址:www.tenouk.com/ModuleCC2.h…
原文作者:www.tenouk.com/
发布时间:约2004年前后
在这个模块中我们有什么?
- 在动态链接库中使用共享内存
- 测试我们的MainDll()
- 函数调用公约
- 在动态链接库中使用线程本地存储技术
- 测试DLL程序
- 动态链接库参考
- 职能
- 过时的功能
我的训练时间: xyz小时。在你开始之前,请阅读这里的一些说明。DLL的Windows MFC编程(GUI编程)可以在MFC GUI编程步骤教程中找到。
Win32的编程技巧。
- 能够理解、构建和运行动态链接库程序。
- 能够区分静态链接和动态链接。
- 能够识别不同类型的动态链接库。
- 能够创建DLL的导出和导入函数。
动态链接库中共享内存的使用。
本节介绍了DLL入口点函数如何使用文件映射对象来设置内存,使加载DLL的进程可以共享内存。共享的DLL内存只在DLL被加载时才会持续存在。本例使用文件映射将一个命名的共享内存块映射到每个加载DLL的进程的虚拟地址空间中。要做到这一点,入口点函数必须。
- 调用CreateFileMapping()函数获得一个文件映射对象的句柄。第一个加载DLL的进程创建文件映射对象。随后的进程打开现有对象的句柄。
- 调用MapViewOfFile()函数将一个视图映射到虚拟地址空间。这样,进程就可以访问共享内存。
请注意,虽然您可以通过为CreateFileMapping()的lpAttributes参数传递一个NULL值来指定默认的安全属性,但您可以选择使用SECURITY_ATTRIBUTES结构来提供额外的安全性。
这是一个空的DLL工程(程序),你需要将你的工程设置为使用__stdcall(使用WINAPI)约定的dllmain()。以下是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);
}
一个示例输出。
将你的项目改为Release版本,并重建DLL程序。 如果没有错误,将DLL文件(本例中为moredll.dll)复制到Windows系统目录。我们将在下一节测试这个DLL文件。
测试我们的MainDll()
让我们通过执行下面的简单程序来测试生成的moredll.dll。 这里没有函数调用(导出)。moredll.dll已经被复制到C:\WINDOWS/System32目录下(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;
}
一个示例输出。
错误。127 -找不到指定的过程。(ERROR_PROC_NOT_FOUND)应该是预期的,因为我们没有在DLL程序中定义任何函数.注意,共享内存可以在每个进程中映射到不同的地址。因此,每个进程都有自己的lpvMem参数实例,它被声明为一个全局变量,因此它对所有DLL函数都可用。本例假设DLL全局数据是不共享的,所以每个加载DLL的进程都有自己的lpvMem实例。
在这个例子中,当最后一个文件映射对象的句柄关闭时,共享内存就会被释放。为了创建持久的共享内存,DLL可以在第一次加载DLL时创建一个分离的进程。如果这个分离的进程使用DLL并且没有终止,它就有一个文件映射对象的句柄,防止共享内存被释放。
函数调用公约
每一次函数调用都会有一个栈帧的创建。 如果我们能研究一下函数调用的操作,以及函数的堆栈框架是如何构造和销毁的,是非常有用的。 对于函数的调用,编译器有一些用于调用的约定。 惯例是指一种标准化的,但不是文档化的标准的做事方式。例如,C/C++的函数调用惯例告诉编译器这样的事情。
- 函数参数被推到堆栈上的顺序。
- 不管是调用函数还是被调用函数(callee)的责任,在调用结束后将参数从栈中删除,这就是栈清理过程。
- 编译器用来标识各个函数的命名约定。
调用约定的例子有__stdcall
,__pascal
,__cdecl
和__fastcall
(对于Microsoft Visual C++)。 调用约定属于一个函数的签名,因此具有不同调用约定的函数之间是不兼容的。 目前C/C++编译器厂商之间,甚至一个编译器的不同版本之间,对于函数调用方案的命名都没有标准。这就是为什么如果你链接了其他编译器编译的对象文件,可能会产生不一样的命名方案,从而导致外挂无法解决。 对于Borland和Microsoft编译器,你可以在返回类型和函数名称之间明确指定一个特定的调用约定,如下所示。
void __cdecl TestFunc(float a, char b, char c); // Borland and Microsoft
或者如前面的例子所示,你可以通过Visual C++/.Net的设置来实现。对于GNU GCC来说,你可以使用__attribute__关键字,在写函数定义时,后面加上关键字__attribute__,然后用双括号说明调用约定,如下图所示。
或者如前面的例子所示,你可以通过Visual C++/.Net的设置来实现。对于GNU GCC来说,你可以使用__attribute__关键字,在写函数定义的时候,后面跟着关键字__attribute__,然后用双括号说明调用约定,如下所示。
void TestFunc(float a, char b, char c) __attribute__((cdecl)); // GNU GCC
以微软Visual C++编译器为例,其使用的函数调用约定有三种,如下表所示。
关键字 | 堆栈清理 | 参数传递 |
---|---|---|
__cdecl | 调用者 | 按相反的顺序(从右到左)推送堆栈中的参数。 调用者清理堆栈。 这是支持变量函数(参数数量可变或类型列表,如printf())的C语言以及C++程序的默认调用惯例。 cdecl 调用约定比 __stdcall 创建更大的可执行文件,因为它要求每个函数调用都包含堆栈清理代码。 |
__stdcall | 被调用者 | 也称为__pascal 。 在堆栈中以相反的顺序(从右到左)推送参数。 使用这种调用惯例的函数需要一个函数原型。 Callee清理堆栈。 这是Win32 API函数中使用的标准约定。 |
__fastcall | 被调用者 | 参数存储在寄存器中,然后推到堆栈上。 __fastcall 调用惯例规定,函数的参数尽可能在寄存器中传递。 Callee清理堆栈。 |
基本上,C函数调用时,调用者会将一些参数推送到堆栈上,调用函数,然后弹出堆栈,清理这些推送的参数。 下面以汇编语言中的__cdecl
为例进行说明。
/*example of __cdecl*/
push arg1
push arg2
call function
add ebp, 12 ;stack cleanup
而对于__stdcall
的例子。
/*example of __stdcall*/
push arg1
push arg2
call function
/* No stack cleanup, it will be done by caller */
在动态链接库中使用线程本地存储技术
本节介绍了使用DLL入口点函数来设置线程本地存储(TLS)索引,为多线程进程的每个线程提供私有存储。 入口点函数使用TlsAlloc()函数在进程加载DLL时分配一个TLS索引。每个线程都可以使用这个索引来存储指向自己内存块的指针。 当用DLL_PROCESS_ATTACH值调用入口点函数时,代码会执行以下操作。
- 使用TlsAlloc()函数分配一个TLS索引。
- 分配一个内存块给进程的初始线程使用。
- 在调用TlsSetValue()函数时使用TLS索引来存储分配到内存的指针。
每当进程创建一个新的线程时,都会用DLL_THREAD_ATTACH值调用入口点函数。然后,entry-point函数为新线程分配一个内存块,并通过使用TLS索引来存储一个指向它的指针。每个线程都可以在调用TlsGetValue()时使用TLS索引来检索自己内存块的指针。
当一个线程终止时,用DLL_THREAD_DETACH值调用入口点函数,该线程的内存被释放。当一个进程终止时,使用DLL_PROCESS_DETACH值调用入口点函数,TLS索引中指针引用的内存被释放。
TLS索引被存储在一个全局变量中,使得所有的DLL函数都可以使用它。下面的例子假定DLL的全局数据是不共享的,因为TLS索引对于每个加载DLL的进程来说不一定是相同的。这是一个空的DLL工程(程序)。
// 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);
}
当一个进程使用该DLL的加载时链接时,切入点函数足以管理线程的本地存储。使用运行时链接的进程可能会出现问题,因为在调用LoadLibrary()函数之前存在的线程没有调用entry-point函数,所以没有为这些线程分配TLS内存。下面的例子解决了这个问题,检查TlsGetValue()函数返回的值,如果该值表明该线程的TLS槽没有设置,则分配内存。
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);
}
将你的项目改为Release模式,然后重建DLL程序。 如果没有错误,将DLL文件(本例中为moredll.dll)复制到Windows系统目录下。我们将在下一节测试该DLL文件。
测试DLL程序
以前面的测试程序为例,让我们测试一下DLL程序。
// 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;
}
一个输出示例。
好吧,我们的DLL程序看起来不错。这一切的人。享受你的C / C++之旅吧
动态链接库参考
以下元素用于动态链接。
功能 | 说明 |
---|---|
DisableThreadLibraryCalls() | 禁用指定DLL的线程附加和线程分离通知。 |
DllMain() | 一个进入DLL的可选入口点。 |
FreeLibrary() | 减少加载的DLL的引用次数。当引用数达到零时,模块将从调用进程的地址空间中解映射。 |
FreeLibraryAndExitThread() | 将加载的DLL的引用数减一,然后调用ExitThread()来终止调用线程。 |
GetDllDirectory() | 检索用于查找应用程序的DLLs的搜索路径的应用程序特定部分。 |
GetModuleFileName() | 检索包含指定模块的文件的完全限定路径。 |
GetModuleFileNameEx() | 检索包含指定模块的文件的完全限定路径。 |
GetModuleHandle() | 读取指定模块的模块句柄。 |
GetModuleHandleEx() | 读取指定模块的模块句柄。 |
GetProcAddress() | 从指定的DLL中检索导出的函数或变量的地址。 |
LoadLibrary() | 将指定的可执行模块映射到调用进程的地址空间。 |
LoadLibraryEx() | 将指定的可执行模块映射到调用进程的地址空间。 |
SetDllDirectory() | 修改用于查找应用程序的DLL的搜索路径。 |
过时的功能
LoadModule()函数只是为了兼容16位版本的Windows而提供的。
进一步的阅读和挖掘
-
Microsoft Visual C++,在线MSDN。
-
结构、枚举、联合和typedef故事可以参考C/C++结构、枚举、联合和typedef。
-
多字节、Unicode字符和本地化请参考Locale、宽字符和Unicode(Story)和Windows用户与组编程教程(Implementation)。
-
Windows数据类型是Windows数据类型。
通过www.DeepL.com/Translator(免费版)翻译