当前位置: 首页 > news >正文

[Windows编程] DLL_THREAD_DETACH 认识误区

DLL 里面使用TLS (Local Thread Storage) 的常见做法是:在DLLMain的DLL_PROCESS_ATTACH/DLL_THREAD_ATTACH 被调用的时候为每个线程(Thread)分配内存,而在DLL_THREAD_DETACH/DLL_PROCESS_DETACH 被调用的时候释放内存。 MSDN文章《Using Thread Local Storage in a Dynamic-Link Library》 上有这样的示例代码。

BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle
DWORD fdwReason, // reason called
LPVOID lpvReserved) // reserved
{
LPVOID lpvData;
BOOL fIgnore;

switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
// Allocate a TLS index.
if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES) return FALSE;
case DLL_THREAD_ATTACH:
lpvData = (LPVOID) LocalAlloc(LPTR, 256);//为每个Thread分配内存
if (lpvData != NULL)
fIgnore = TlsSetValue(dwTlsIndex, lpvData);
break;
case DLL_THREAD_DETACH:
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData); //释放内存
break;
case DLL_PROCESS_DETACH:
lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData); //释放内存
TlsFree(dwTlsIndex);
break;
default:
break;
}
return TRUE;
}

这段代码认为DLL_THREAD_DETACH 总是会被调用, 但实际情况并非如此。在某些情况下DLL_THREAD_DETACH并不会被调用, 结果造成内存泄漏。 接下来做2个简单实验说明这个问题。

实验代码:

typedef void (__stdcall *FNSLEEP)();

void CallTestDLL()
{
FNSLEEP pfnSleep = (FNSLEEP)::GetProcAddress(g_hDLLModule, "DoSleep");
ATLASSERT(pfnSleep);
(*pfnSleep)();
}

DWORD WINAPI ThreadProc( LPVOID lpParam)
{
CallTestDLL();
return 0;
}

g_hDLLModule = ::LoadLibrary(_T("TestDLL.dll"));
ATLTRACE("[Thread %d] LoadLibrary=0x%.8x\n", ::GetCurrentThreadId());
CallTestDLL();
const int MAX_THREAD = 2;
HANDLE hThread[MAX_THREAD];
for (int i=0; i < MAX_THREAD; i++)
{
hThread[i] = ::CreateThread(NULL, 0, ThreadProc, 0, 0, NULL);
}
Sleep(MAX_THREAD * 1000);
::FreeLibrary(g_hDLLModule);

输出结果1:

[Thread 4976] DLL_PROCESS_ATTACH //主线程
[Thread 4976] LoadLibrary=0x0ecbf9d4
[Thread 4976] DoSleep() in DLL
[Thread 7860] DLL_THREAD_ATTACH //CreateThread 产生的线程
[Thread 736] DLL_THREAD_ATTACH//CreateThread 产生的线程
[Thread 736] DoSleep() in DLL
[Thread 7860] DoSleep() in DLL
[Thread 736] DLL_THREAD_DETACH
[Thread 7860] DLL_THREAD_DETACH
[Thread 4976] DLL_PROCESS_DETACH //主线程

以上输入结果我们看到每个Thread 调用DLL函数DoSleep 立即结束,这时候DLL_THREAD_DETACH 被正常调用。 这时只要候稍微改一下代码,会看到完全不同的结果。

DWORD WINAPI ThreadProc( LPVOID lpParam)
{
CallTestDLL();

DoSomethingElse(); //延迟线程结束
return 0;
}

输出结果2:

[Thread 7448] DLL_PROCESS_ATTACH //主线程
[Thread 7448] LoadLibrary=0x0b1cf9d4
[Thread 7448] DoSleep() in DLL
[Thread 6872] DLL_THREAD_ATTACH
[Thread 6556] DLL_THREAD_ATTACH
[Thread 6556] DoSleep() in DLL
[Thread 6872] DoSleep() in DLL
[Thread 7448] DLL_PROCESS_DETACH //主线程

我们发现,CreateThread 产生的线程并没有调用DLL_THREAD_DETACH

结论:

如果是线程在DLL被卸载(调用FreeLibrary) 之前结束,则DLL_THREAD_DETACH 会被调用。 如果线程在DLL卸载之后结束,则DLL_THREAD_DETACH 不会被调用。

相关文章:

  • LeetCode -- Bulb Switcher
  • 我在业务架构平台研讨会上的演讲视频
  • leet code -- Swap Nodes in Pairs
  • LeetCode -- Range Sum Query - Immutable
  • 嵌入式产品认证相关的小知识
  • leet code - Third Maximum Number
  • 关于中国软件发展的讨论沙龙
  • SQL server replication的三种方式
  • leetcode -- Count Numbers with Unique Digits
  • javascript小数四舍五入
  • 业务场景和业务用例场景的区别(作者:Arthur网友)
  • Android -- 打开时隐藏软键盘
  • 邀请大象一书的读者和广大网友写关于分析设计、建模方面的自愿者文章
  • Android -- 读取NFC卡号
  • Windows 7安装以及VS2008和Office2007冲突的问题
  • Android单元测试 - 几个重要问题
  • Babel配置的不完全指南
  • Create React App 使用
  • CSS进阶篇--用CSS开启硬件加速来提高网站性能
  • ES6之路之模块详解
  • java8 Stream Pipelines 浅析
  • Java的Interrupt与线程中断
  • Markdown 语法简单说明
  • STAR法则
  • uni-app项目数字滚动
  • Vultr 教程目录
  • 从0实现一个tiny react(三)生命周期
  • 类orAPI - 收藏集 - 掘金
  • 学习ES6 变量的解构赋值
  • 硬币翻转问题,区间操作
  • 测评:对于写作的人来说,Markdown是你最好的朋友 ...
  • ​io --- 处理流的核心工具​
  • # Apache SeaTunnel 究竟是什么?
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • #ifdef 的技巧用法
  • #mysql 8.0 踩坑日记
  • #stm32驱动外设模块总结w5500模块
  • (1)STL算法之遍历容器
  • (11)MATLAB PCA+SVM 人脸识别
  • (9)STL算法之逆转旋转
  • (Java实习生)每日10道面试题打卡——JavaWeb篇
  • (Mac上)使用Python进行matplotlib 画图时,中文显示不出来
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (pojstep1.3.1)1017(构造法模拟)
  • (ros//EnvironmentVariables)ros环境变量
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (分类)KNN算法- 参数调优
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (转)详解PHP处理密码的几种方式
  • ./mysql.server: 没有那个文件或目录_Linux下安装MySQL出现“ls: /var/lib/mysql/*.pid: 没有那个文件或目录”...
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .NET MVC第五章、模型绑定获取表单数据
  • .NET MVC之AOP
  • .net的socket示例