您的位置:首页 > 编程语言 > C语言/C++

动态链接库学习

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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ 函数 动态库 dll