使用动态链接库 (Dynamic-Link 库)

Qt源码解析 索引

计算机基础-使用动态链接库 (Dynamic-Link 库)

目录

Qt源码解析 索引

计算机基础-使用动态链接库 (Dynamic-Link 库)

创建简单的动态链接库

使用加载时动态链接

使用运行时动态链接

在动态链接库中使用共享内存

实现共享内存的 DLL

使用共享内存的进程

使用动态链接库中的线程本地存储

Dynamic-Link库函数


创建简单的动态链接库

下面的示例是创建简单 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 后,可以使用它在应用程序中定义的函数。 下面是一个简单的控制台应用程序,它使用从 Myputs.dll 导出的 myPuts 函数 。

由于此示例显式调用 DLL 函数,因此必须将应用程序的模块与导入库 Myputs。

#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 的句柄 (请参阅创建简单Dynamic-Link 库) 。 如果 LoadLibrary 成功,程序将使用 GetProcAddress 函数中返回的句柄获取 DLL 的 myPuts 函数的地址。 调用 DLL 函数后,程序将调用 FreeLibrary 函数来卸载 DLL。

由于程序使用运行时动态链接,因此不需要将模块与 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 时保留。 应用程序可以使用 SetSharedMem 和 GetSharedMem 函数访问共享内存。

实现共享内存的 DLL

该示例使用文件映射将命名共享内存块映射到加载 DLL 的每个进程的虚拟地址空间。 为此,入口点函数必须:

  1. 调用 CreateFileMapping 函数获取文件映射对象的句柄。 加载 DLL 的第一个进程创建文件映射对象。 后续进程将打开现有 对象的句柄。 有关详细信息,请参阅 Creating a File-Mapping Object。

  2. 调用 MapViewOfFile 函数将视图映射到虚拟地址空间。 这使进程能够访问共享内存。 有关详细信息,请参阅 创建文件视图。

// 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 实例,该实例声明为全局变量,以便它可供所有 DLL 函数使用。 该示例假定 DLL 全局数据不共享,因此加载 DLL 的每个进程都有自己的 lpvMem 实例。

请注意,当文件映射对象的最后一个句柄关闭时,将释放共享内存。 若要创建永久性共享内存,需要确保某些进程始终具有文件映射对象的打开句柄。

使用共享内存的进程

以下进程使用上面定义的 DLL 提供的共享内存。 第一个进程调用 SetSharedMem 编写字符串,而第二个进程调用 GetSharedMem 来检索此字符串。

此过程使用 DLL 实现的 SetSharedMem 函数将字符串"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;
}

此过程使用 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 的每个进程不一定相同。

每当进程加载 DLL 时,入口点函数都使用 TlsAlloc 函数分配 TLS 索引。 然后,每个线程都可以使用此索引来存储指向其自己的内存块的指针。

使用 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

Dynamic-Link库函数

函数说明
AddDllDirectory将目录添加到进程 DLL 搜索路径。
DisableThreadLibraryCalls禁用指定 DLL 的线程附加和线程分离通知。
DllMainDLL 中的可选入口点。
FreeLibrary减已加载 DLL 的引用计数。 当引用计数达到零时,将取消映射调用进程的地址空间中的模块。
FreeLibraryAndExitThread将加载的 DLL 的引用计数减 1,然后调用 ExitThread 终止调用线程。
GetDllDirectory检索用于查找应用程序的 DLL 的搜索路径的应用程序特定部分。
GetModuleFileName检索包含指定模块的文件的完全限定路径。
GetModuleFileNameEx检索包含指定模块的文件的完全限定路径。
GetModuleHandle检索指定模块的模块句柄。
GetModuleHandleEx检索指定模块的模块句柄。
GetProcAddress从指定的 DLL 检索导出的函数或变量的地址。
LoadLibrary映射指定的可执行模块转换为调用进程的地址空间。
LoadLibraryEx映射指定的可执行模块转换为调用进程的地址空间。
LoadPackagedLibrary映射打包模块及其依赖项集成到调用进程的地址空间中。 只有Windows应用商店应用才能调用此函数。
RemoveDllDirectory使用 AddDllDirectory删除已添加到进程 DLL 搜索路径的目录。
SetDefaultDllDirectories指定在调用进程加载 DLL 时要搜索的默认目录集。
SetDllDirectory修改用于查找应用程序的 DLL 的搜索路径。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

道格拉斯范朋克

播种花生牛奶自留田

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值