当前位置: 首页 > 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 不会被调用。

 

相关文章:

  • 这个时代,传统媒体是什么?能做什么?
  • sql_根据父栏目id获取子栏目
  • 闪存、内存涨价贡献大:美光2017财年第二季度营收暴涨58%
  • 数据恢复软件比较
  • 为什么说你不是大咖? | 记BingoDay2017
  • 页面执行完后触发客户端脚本的方法
  • 一篇短文告诉你阿里云用户如何通过等保测评
  • C++注释规范
  • 使用tmpfs优化firefox
  • 我们的一个已投产项目的高可用数据库实战 - mongo 副本集的搭建具体过程
  • asp.net 返回上一页的实现方法小集
  • IbatisNet连接oracle 报错
  • 文章
  • 为长途旅行提供免费WiFi服务是不是一桩靠谱的生意
  • squid配置
  • [nginx文档翻译系列] 控制nginx
  • [译]Python中的类属性与实例属性的区别
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • C++类的相互关联
  • EventListener原理
  • exif信息对照
  • iOS小技巧之UIImagePickerController实现头像选择
  • Js基础——数据类型之Null和Undefined
  • Lucene解析 - 基本概念
  • Objective-C 中关联引用的概念
  • OSS Web直传 (文件图片)
  • React+TypeScript入门
  • 记录一下第一次使用npm
  • 区块链将重新定义世界
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 通过npm或yarn自动生成vue组件
  • 网页视频流m3u8/ts视频下载
  • 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!
  • 组复制官方翻译九、Group Replication Technical Details
  • ​520就是要宠粉,你的心头书我买单
  • #Z0458. 树的中心2
  • $.ajax,axios,fetch三种ajax请求的区别
  • (01)ORB-SLAM2源码无死角解析-(56) 闭环线程→计算Sim3:理论推导(1)求解s,t
  • (2)(2.4) TerraRanger Tower/Tower EVO(360度)
  • (2009.11版)《网络管理员考试 考前冲刺预测卷及考点解析》复习重点
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (初研) Sentence-embedding fine-tune notebook
  • (黑客游戏)HackTheGame1.21 过关攻略
  • .java 9 找不到符号_java找不到符号
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例
  • .NET 发展历程
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • .Net中的设计模式——Factory Method模式
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面
  • @html.ActionLink的几种参数格式
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解
  • [].slice.call()将类数组转化为真正的数组
  • [android]-如何在向服务器发送request时附加已保存的cookie数据