动态链接库学习
2017-11-15 08:05
183 查看
动态链接库
动态链接库是一个模块,它包含一些函数和数据,这些函数和数据可以被其它模块(程序或者动态链接库)所使用。当它被另一个动态链接库调用的时候,可以认为动态链接库可以嵌套调用。
一个动态链接库可以定义两种函数:导出函数和内部函数。
导出函数的用处是被其它模块调用,当然也可以被定义该函数的动态链接库中的函数调用。
内部函数一般只是被定义该函数的动态链接库中的函数使用。
尽管动态链接库可以导出数据,但是通常它的数据仅仅被自己的函数使用。
然而,你无法阻止其他模块对那个数据地址进行读取。
动态链接库提供了一种方法,该方法可以将程序模块化,因此动态链接库可以很容易的被更新和重复使用。
当多个应用程序同时使用相同的功能时,动态链接库可以帮助减少内存开销。
因为,即使每个应用程序会拷贝该DLL中数据的副本,但是该DLL源代码本身是共享的。
API是作为一系列DLL来实现的,所以使用了Windows的API的任何进程都使用了动态链接方式。
使用动态链接库
创建一个简单的动态链接库
下面的例子是创建一个简单的DLL所需要的源代码,Myputs.dll。它定义了一个简单的字符串打印函数:myPuts。Myputs DLL没有定义进入点函数,因为它连接到C运行时库并且它自己没有需要执行的初始化或者清理函数。
要创建DLL,按照以下文档的指导完成, 文档包含在你的开发工具中。
// The myPuts function writes a null-terminated string to // the standard output device. // The export mechanism used here is the __declspec(export) // method supported by Microsoft Visual Studio, but any // other export method supported by your development // environment may be substituted. #include <windows.h> #define EOF (-1) #ifdef __cplusplus // If used by C++ code, extern "C" { // we need to export the C interface #endif __declspec(dllexport) int __cdecl myPuts(LPWSTR lpszMsg) { DWORD cchWritten; HANDLE hConout; BOOL fRet; // Get a handle to the console output device. hConout = CreateFileW(L"CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (INVALID_HANDLE_VALUE == hConout) return EOF; // Write a null-terminated string to the console output device. while (*lpszMsg != L'\0') { fRet = WriteConsole(hConout, lpszMsg, 1, &cchWritten, NULL); if( (FALSE == fRet) || (1 != cchWritten) ) return EOF; lpszMsg++; } return 1; } #ifdef __cplusplus } #endif
使用加载时动态链接
当你创建一个DLL之后,你可以在一个应用程序中使用该DLL定义的函数。下面是一个简单的控制台应用程序,该应用程序使用了myPuts.dll导出的myPuts函数。
因为该例子使用显示方式调用myPuts.dll, 应用程序的该DLL模块必须与导入库(myPuts.lib)链接在一起。
要获取更多关于创建DLL的信息,参看你的开发工具中的说明文档。
#include <windows.h> extern "C" int __cdecl myPuts(LPWSTR); // a function from a DLL int main(VOID) { int Ret = 1; Ret = myPuts(L"Message sent to the DLL function\n"); return Ret; }
使用运行时动态链接
对于同一个DLL,你可以使用两种方式加载,加载时和运行时。下面的例子使用LoadLibrary这个函数来获取myPuts.dll的句柄。
如果LoadLibrary函数成功,程序使用返回的句柄作为GetProcAddress函数的参数,来获取DLL中的myPuts函数的地址。
调用myPuts.dll中的函数之后,程序需要调用FreeLibrary函数来释放该myPuts.dll。
因为程序使用运行时动态链接,链接该myPuts模块时不需要使用该myPuts.dll的导入库。
这个例子展示了运行时和加载时动态链接的不同。如果DLL不可用,那么使用加载时动态链接的程序必须终止进程。
然而,运行时动态加载示例可以提示一个错误而不必终止进程。
// A simple program that uses LoadLibrary and // GetProcAddress to access myPuts from Myputs.dll. #include <windows.h> #include <stdio.h> typedef int (__cdecl *MYPROC)(LPWSTR); int main( void ) { HINSTANCE hinstLib; MYPROC ProcAdd; BOOL fFreeResult, fRunTimeLinkSuccess = FALSE; // Get a handle to the DLL module. hinstLib = LoadLibrary(TEXT("MyPuts.dll")); // If the handle is valid, try to get the function address. if (hinstLib != NULL) { ProcAdd = (MYPROC) GetProcAddress(hinstLib, "myPuts"); // If the function address is valid, call the function. if (NULL != ProcAdd) { fRunTimeLinkSuccess = TRUE; (ProcAdd) (L"Message sent to the DLL function\n"); } // Free the DLL module. fFreeResult = FreeLibrary(hinstLib); } // If unable to call the DLL function, use an alternative. if (! fRunTimeLinkSuccess) printf("Message printed from executable\n"); return 0; }
在动态链接库中使用共享内存
下面的例子展示了 DLL进入点函数是怎样使用文件映射对象来设置可以被不同进程共享的内存,只要这些进程加载了该DLL。只要DLL被加载,共享DLL内存就会持续存在。应用程序可以使用SetShareMem和GetShareMem函数来访问这些共享内存。
实现了共享内存的DLL
下面的例子使用了文件映射去映射一个叫做共享内存的块到加载该DLL的每个进程自己的虚拟地址空间。为了完成这个工作,进入点函数必须完成如下工作:
调用CreateFileMapping函数去获取一个文件映射对象的句柄。加载该DLL的第一个进程创建该文件映射对象。
后续的进程打开一个已经存在的对象的句柄。
调用MapViewOfFile函数来映射一个视图到虚拟地址空间。这使得进程可以访问共享内存。
注意:你可以通过为lpAttributes参数传递一个NULL值来指定默认安全属性,你也可以选择SECURITY_ATTRIBUTES结构体来提供额外的安全属性。
// The DLL code #include <windows.h> #include <memory.h> #define SHMEMSIZE 4096 static LPVOID lpvMem = NULL; // pointer to shared memory static HANDLE hMapObject = NULL; // handle to file mapping // The DLL entry-point function sets up shared memory using a // named file-mapping object. BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle DWORD fdwReason, // reason called LPVOID lpvReserved) // reserved { BOOL fInit, fIgnore; switch (fdwReason) { // DLL load due to process initialization or LoadLibrary case DLL_PROCESS_ATTACH: // 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 TEXT("dllmemfilemap")); // name of map object if (hMapObject == NULL) return FALSE; // 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; // 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: break; // The thread of the attached process terminates case DLL_THREAD_DETACH: break; // DLL unload due to process termination or FreeLibrary case DLL_PROCESS_DETACH: // 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: break; } return TRUE; UNREFERENCED_PARAMETER(hinstDLL); UNREFERENCED_PARAMETER(lpvReserved); } // The export mechanism used here is the __declspec(export) // method supported by Microsoft Visual Studio, but any // other export method supported by your development // environment may be substituted. #ifdef __cplusplus // If used by C++ code, extern "C" { // we need to export the C interface #endif // SetSharedMem sets the contents of the shared memory __declspec(dllexport) VOID __cdecl SetSharedMem(LPWSTR lpszBuf) { LPWSTR lpszTmp; DWORD dwCount=1; // Get the address of the shared memory block lpszTmp = (LPWSTR) lpvMem; // Copy the null-terminated string into shared memory while (*lpszBuf && dwCount<SHMEMSIZE) { *lpszTmp++ = *lpszBuf++; dwCount++; } *lpszTmp = '\0'; } // GetSharedMem gets the contents of the shared memory __declspec(dllexport) VOID __cdecl GetSharedMem(LPWSTR lpszBuf, DWORD cchSize) { LPWSTR lpszTmp; // Get the address of the shared memory block lpszTmp = (LPWSTR) lpvMem; // Copy from shared memory into the caller's buffer while (*lpszTmp && --cchSize) *lpszBuf++ = *lpszTmp++; *lpszBuf = '\0'; } #ifdef __cplusplus } #endif
共享内存可以被映射到每个进程的不同地址上。因此,每个进程有自己的lpvMem实例。
这个lpvMem被声明为全局变量,以至于所有的DLL函数可以使用。
该例子假设DLL全局数据不是共享的,所以每个加载DLL的进程有它自己的lpvMem实例。
注意共享内存在最后的文件映射对象关闭的时候就被释放掉了。
为了创建永久的共享内存,你应该保证一些进程始终有对文件映射对象的打开句柄。
使用共享内存的进程
下面的进程使用了上述定义的dLL提供的共享内存。第一个进程调用SetSharedMem函数来写一个字符串,而第二个进程调用了GetSharedMem函数来获取这个字符串。
下面这个进程使用了该DLL的SetShareMem函数来写“This is a test string”字符串到共享内存中。
它开启了一个子进程,这个子进程将会从这个共享内存中读取字符串。
// Parent process #include <windows.h> #include <tchar.h> #include <stdio.h> extern "C" VOID __cdecl SetSharedMem(LPWSTR lpszBuf); HANDLE CreateChildProcess(LPTSTR szCmdline) { PROCESS_INFORMATION piProcInfo; STARTUPINFO siStartInfo; BOOL bFuncRetn = FALSE; // Set up members of the PROCESS_INFORMATION structure. ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) ); // Set up members of the STARTUPINFO structure. ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); siStartInfo.cb = sizeof(STARTUPINFO); // Create the child process. bFuncRetn = CreateProcess(NULL, szCmdline, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited 0, // creation flags NULL, // use parent's environment NULL, // use parent's current directory &siStartInfo, // STARTUPINFO pointer &piProcInfo); // receives PROCESS_INFORMATION if (bFuncRetn == 0) { printf("CreateProcess failed (%)\n", GetLastError()); return INVALID_HANDLE_VALUE; } else { CloseHandle(piProcInfo.hThread); return piProcInfo.hProcess; } } int _tmain(int argc, TCHAR *argv[]) { HANDLE hProcess; if (argc == 1) { printf("Please specify an input file"); ExitProcess(0); } // Call the DLL function printf("\nProcess is writing to shared memory...\n\n"); SetSharedMem(L"This is a test string"); // Start the child process that will read the memory hProcess = CreateChildProcess(argv[1]); // Ensure this process is around until the child process terminates if (INVALID_HANDLE_VALUE != hProcess) { WaitForSingleObject(hProcess, INFINITE); CloseHandle(hProcess); } return 0; }
下面这个进程使用ShareMemory.dll中实现的GetSharedMem函数去从共享内存中读取一个字符串。
它由上面的父进程创建出来的。
// Child process #include <windows.h> #include <tchar.h> #include <stdio.h> extern "C" VOID __cdecl GetSharedMem(LPWSTR lpszBuf, DWORD cchSize); int _tmain( void ) { WCHAR cBuf[MAX_PATH]; GetSharedMem(cBuf, MAX_PATH); printf("Child process read from shared memory: %S\n", cBuf); return 0; }
在动态链接库中使用线程本地存储
这个部分展示了DLL进入点函数的使用方式,用来设置一个TLS(线程本地存储),来为多线程进程中的每个线程提供私有存储。TLS索引存储在全局变量中,对所有的DLL函数都可见。
本例假设DLL的全局数据不共享,因为TLS索引对于加载该DLL的进程来说不需要一致。
进入点函数使用TlsAlloc函数来分配一个TLS索引,而不管何时进程加载该DLL都可以实现。
每个线程可以使用这个索引去存储一个指向它自己的内存块的指针。
当进入点函数带有DLL_PROCESS_ATTACH的值被调用,代码执行如下动作:
使用TlsAlloc函数来分配TLS索引
通过进程的初始化线程去分配一个内存块,该内存块被用来独立使用
在TlsSetValue函数中使用TLS索引去存储与该索引相关的TLS插槽中内存块的地址。
每次进程创建了一个新的线程,那么进入点函数就会附加DLL_THREAD_ATTACH的值来被调用。
进入点函数然后会为新的线程分配一个内存块,并且使用该TLS索引存储一个指向该内存块的的指针。
当一个函数需要访问一些数据,该数据与一个TLS索引相关,那么指定TlsGetValue函数中的索引参数即可。
这可以获得调用线程的TLS插槽的内容,这些内容是数据内存块的指针。
当一个进程使用该DLL进行加载时链接时,进入点函数对于管理线程本地存储是足够的。
当一个进程使用运行时动态加载时可能会出现问题,因为在LoadLibrary函数调用之前,
对于已经存在的线程,并没有调用进入点函数。所以TLS内存对于这些线程是不支持的。
下面这个例子解决了这个问题,方法是检查TlsGetValue函数的返回值,并且当返回值表示该线程的TLS插槽没有被设置时,自动分配内存。
当一个线程不再使用TLS索引时, 该进程必须要释放保存在TLS插槽中的内存。
当所有的线程结束使用TLS索引时,使用TlsFree函数来释放这个索引。
当一个线程结束时,进入点函数伴随着DLL_THREAD_DETACH的值被调用,并且那个线程的内存被释放。
当一个进程结束时,进入点函数伴随着DLL_PROCESS_DETACH的值被调用,并且TLS索引中的指针指向的内存也被释放了。
// The DLL code #include <windows.h> static DWORD dwTlsIndex; // address of shared memory // DllMain() is the entry-point function for this DLL. BOOL WINAPI 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: // Allocate a TLS index. if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) return FALSE; // No break: Initialize the index for first thread. // The attached process creates a new thread. case DLL_THREAD_ATTACH: // 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: // 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: // Release the allocated memory for this thread. lpvData = TlsGetValue(dwTlsIndex); if (lpvData != NULL) LocalFree((HLOCAL) lpvData); // Release the TLS index. TlsFree(dwTlsIndex); break; default: break; } return TRUE; UNREFERENCED_PARAMETER(hinstDLL); UNREFERENCED_PARAMETER(lpvReserved); } // The export mechanism used here is the __declspec(export) // method supported by Microsoft Visual Studio, but any // other export method supported by your development // environment may be substituted. #ifdef __cplusplus // If used by C++ code, extern "C" { // we need to export the C interface #endif __declspec(dllexport) BOOL WINAPI StoreData(DWORD dw) { LPVOID lpvData; DWORD * pData; // The stored memory pointer lpvData = TlsGetValue(dwTlsIndex); if (lpvData == NULL) { lpvData = (LPVOID) LocalAlloc(LPTR, 256); if (lpvData == NULL) return FALSE; if (!TlsSetValue(dwTlsIndex, lpvData)) return FALSE; } pData = (DWORD *) lpvData; // Cast to my data type. // In this example, it is only a pointer to a DWORD // but it can be a structure pointer to contain more complicated data. (*pData) = dw; return TRUE; } __declspec(dllexport) BOOL WINAPI GetData(DWORD *pdw) { LPVOID lpvData; DWORD * pData; // The stored memory pointer lpvData = TlsGetValue(dwTlsIndex); if (lpvData == NULL) return FALSE; pData = (DWORD *) lpvData; (*pdw) = (*pData); return TRUE; } #ifdef __cplusplus } #endif
下面的代码展示了前面例子中定义的DLL函数的使用。
#include <windows.h> #include <stdio.h> #define THREADCOUNT 4 #define DLL_NAME TEXT("testdll") VOID ErrorExit(LPSTR); extern "C" BOOL WINAPI StoreData(DWORD dw); extern "C" BOOL WINAPI GetData(DWORD *pdw); DWORD WINAPI ThreadFunc(VOID) { int i; if(!StoreData(GetCurrentThreadId())) ErrorExit("StoreData error"); for(i=0; i<THREADCOUNT; i++) { DWORD dwOut; if(!GetData(&dwOut)) ErrorExit("GetData error"); if( dwOut != GetCurrentThreadId()) printf("thread %d: data is incorrect (%d)\n", GetCurrentThreadId(), dwOut); else printf("thread %d: data is correct\n", GetCurrentThreadId()); Sleep(0); } return 0; } int main(VOID) { DWORD IDThread; HANDLE hThread[THREADCOUNT]; int i; HMODULE hm; // Load the DLL hm = LoadLibrary(DLL_NAME); if(!hm) { ErrorExit("DLL failed to load"); } // Create multiple threads. for (i = 0; i < THREADCOUNT; i++) { hThread[i] = CreateThread(NULL, // default security attributes 0, // use default stack size (LPTHREAD_START_ROUTINE) ThreadFunc, // thread function NULL, // no thread function argument 0, // use default creation flags &IDThread); // returns thread identifier // Check the return value for success. if (hThread[i] == NULL) ErrorExit("CreateThread error\n"); } WaitForMultipleObjects(THREADCOUNT, hThread, TRUE, INFINITE); FreeLibrary(hm); return 0; } VOID ErrorExit (LPSTR lpszMessage) { fprintf(stderr, "%s\n", lpszMessage); ExitProcess(0); }
相关文章推荐
- 动态链接库基础学习一
- 动态链接库的学习-1
- 【JNI调用DLL动态库】Java使用JNI调用DLL动态链接库学习记录
- 学习 gcc编译器使用3 生成动态链接
- 动态链接库学习-2
- [NOTE] Windows&Linux动态链接库学习笔记
- 动态链接库基础学习二
- 关于动态链接学习中的小结
- COM 技术内幕学习之五 (动态链接)
- 2017.04.13 有关动态链接库的学习
- NDK学习笔记:动态链接库与静态链接库的基本使用流程简记
- Linux入门学习-gcc编译器与静态动态链接_第五章
- 【深度学习】环境搭建出现“无法定位程序输入点于动态链接库mkl.dll上“问题
- 动态链接库创建与使用(学习笔记)
- 一些自己学习的html代码(锚,动态改变文本和链接,有序无序列表等)
- 动态链接库学习
- 动态链接库创建与使用(学习笔记)
- 动态链接库创建与使用(学习笔记) .
- JavaScript精简学习4(动态表单和链接处理)
- 动态链接库的初步学习