首页 > 其他分享 >Dll堆栈问题(Dll的静态变量与全局变量、vs的MT与MD)

Dll堆栈问题(Dll的静态变量与全局变量、vs的MT与MD)

时间:2024-03-21 17:33:33浏览次数:39  
标签:MD exe 静态 dll MT 全局变量 Dll 链接

问题引入:
dll有一个导出函数,函数参数是string&,string在函数内部被=赋值。在exe动态加载此dll,调用此导出函数后,会崩溃。

原因:
如果任何STL类的实现中使用了静态变量(我们无从得知但map、string存在此问题),且编译dll时,vc的运行库设置为MT或MTd,会静态链接VC的运行时库,这会导致采用静态链接的方式将导致生成的目标模块拥有独立的堆栈空间。即此静态变量在dll中有独立的一份,在任何加载此dll的exe中,也有独立的一份。

由于"谁申请,谁释放"的原则,如果按照上面的情景,由于string在dll的函数内部被赋值,因此是由dll去申请的内存。但是因为string&传递了出去,在exe中使用,是由exe释放的string。因此会导致崩溃。

解决方法:
方法1.如果任何STL类内部使用了静态变量(无论是直接还是间接使用),那么就不要再写出跨执行单元访问它的代码。
方法2.对于跨模块使用string&,改为使用WCHAR*
方法3.编译dll时,修改vc的运行库为MD或MDd,这样堆栈空间就是共享的了。


 /MD 与 /MT、/MTd与/MDd的区别

1./MD 与 /MT 用于Release 版本,前者表示链接时,不链接VC的运行时库(msvcrt.lib),而采用动态库(msvcrtXX.dll,其中XX表示使用的版本);相应地,后者则表示静态链接VC的运行时库,这样的结果是链接生成的的目标模块体积明显比前者要大一些。

2、/MDD与/MTD 用于Debug版本,其它规则同上。

3、除了在是动、静态链接VC运行时库上有区别,另外的区别点在于,采用静态链接的方式将导致生成的目标模块拥有独立的堆栈空间,如果生成的是DLL,那意味着调用该DLL的EXE程序与该DLL有着不同的堆栈空间,如果发生了EXE拿到了在DLL中分配内存创建的对象,在EXE对其进行析构时,就会导致内存非法访问,出现类似于“ windows已在XX.exe中触发一个断点... ...”的错误。所以,尽量不要使用 /MT与/MTD进行静态运行时库链接的方式,即使要使用,也一定要遵循“谁申请,谁释放”的原则。但是该原则在使用类时很难遵循,因为类中可能会有申请内存的动作。

4、采用第1点静态链接时,如果生成的模块拿给别人使用,别人若使用了不同版本的编译器,则会在链接时产生一系列问题,比如经常需要手动忽略 msvcrt.lib这个库。具体会导致的问题此处不做研究。

5.另外,多模块程序的内存空间很值得推敲研究。但Linux下貌似不存在这些问题。


DLL的全局变量与静态变量

微软解释:https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-data

在 DLL 源代码文件中声明为全局的变量被编译器和链接器视为全局变量,如果一个exe加载了此dll,那么这个全局变量对于exe和dll来说是相同的;但加载给定 DLL 的每个进程都会获得该 DLL 全局变量的自己的副本,这个全局变量对于不同进程是不同的。

静态变量的作用域仅限于声明静态变量的块内,静态全局变量(函数)在本编译单元之外不可见。如果某个编译单元exe...(目标文件)包含了dll的这个.h文件,那么他会获得一份此静态全局变量的副本。这两份静态全局变量是不一样的。

因此,默认情况下,每个进程都有自己的 DLL 全局变量和静态变量实例。


当然我们可以使用共享数据段在不同进程间共享dll的全局变量,当然我们必须明确限制同时访问,例如使用命名互斥锁。

动态链接库
#ifdef MYDLL_EXPORTS
    define MYDLL_API __declspec(dllexport)
#else
    define MYDLL_API __declspec(dllimport)
#endif

MYDLL_API extern int latchCounter;


DLL文件
#include "dll.h"

#pragma data_seg(".shared")
int latchCounter = 0;        // 定义在共享数据段的全局变量,可以在不同进程中共享

#pragma data_seg()
// the 'S' here is the key to mark the ".shared" data segment as shared
#pragma comment(linker, "/SECTION:.shared,RWS")

主程序
#include <type_traits>
#include <memory>
#include <Windows.h>
#include "dll.h"

template<auto f>

using fn_constant = std::integral_constant<decltype(f), f>;

using handle_ptr = std::unique_ptr<void, fn_constant<&CloseHandle>>;

int main() 
{
    // increase latchCounter
    {
        handle_ptr mx{ CreateMutex(nullptr, false, L"MyLatchCounterMutex") };
 
        WaitForSingleObject(mx.get(), INFINITE);
        ++latchCounter;
        ReleaseMutex(mx);
    }

    // decrease latchCounter
    {
        handle_ptr mx{ CreateMutex(nullptr, false, L"MyLatchCounterMutex") };
 
        WaitForSingleObject(mx.get(), INFINITE);
 
        if (--latchCounter == 0) 
        {
            // do something
        }
        ReleaseMutex(mx);
    }
}

 

测试上面理论的用例:

//dll.h:

#include <tchar.h>
#include <iostream>
#include <vector>

#ifdef MATHLIB_EXPORT
#define MATHLIBAPI __declspec(dllexport)
#else
#define MATHLIBAPI __declspec(dllimport)
#endif

extern MATHLIBAPI int i;        // 导出全局变量,在同一个进程中是可见的

//extern static int iStatic;    // 提示错误,不能导出,因为静态全局在本编译单元之外不可见。
static int i = 1;    // dll中的静态全局变量(函数)在本编译单元之外不可见,
                     // 如果某个编译单元exe...(目标文件)包含了dll的这个.h文件,那么他会获得一份此静态全局变量的副本。

//dll.cpp:

int i = 1;

extern "C" __declspec(dllexport) void APIENTRY DllTestStatic(std::vector<std::wstring>& vecStr);

extern "C" __declspec(dllexport) void APIENTRY DllTestStatic2();

__declspec(dllexport) void APIENTRY DllTestStatic(std::vector<std::wstring>& vecStr)
{
    vecStr.push_back(_T("11111"));    // 申请wstring是在dll内部申请的
}

__declspec(dllexport) void APIENTRY DllTestStatic2()
{
    wprintf(_T("全局变量 dll_iInt = %d\n"), i);

    wprintf(_T("静态全局变量 dll_iStatic = %d\n"), iStatic);
}


EXE的代码:

#include <vector>
#include "../Dll_Static/dllexprot.h"    // 实验3、4

//typedef void(*FuncDllTestStatic)(std::vector<std::wstring>& vecStr);    // 实验1
//typedef void(*FuncDllTestStatic2)();    // 实验4

/*
  // 实验1
  // 测试dll动态加载,传递string& - 设置MD运行库的dll不会崩溃,设置MT的会崩溃

  // 崩溃原因:
  // 采用(MT)静态链接的方式链接msvcrt.lib将导致生成的目标模块dll拥有独立的堆栈空间,和exe不是一个堆栈空间
  // 由于stl中很多都具有static变量,这样申请wstring是在dll内部申请的,但是释放却是在exe中。导致崩溃。

  std::vector<std::wstring> vecStr;
  HMODULE ModuleBase = LoadLibrary(_T("Dll_Static.dll"));
  FuncDllTestStatic pDllTestStatic = (FuncDllTestStatic)GetProcAddress(ModuleBase, "DllTestStatic");
  pDllTestStatic(vecStr);
*/

//------------------------------------------------------------------------------------------

/*
    // 实验2
    // 测试dll隐式链接,传递string& - 设置MD运行库的dll不会崩溃,设置MT的会崩溃,原因如上
    std::vector<std::wstring> vecStr;
    DllTestStatic(vecStr);
*/

//------------------------------------------------------------------------------------------

/*
    // 实验3
    // 测试dll隐式链接 - 静态全局变量是不可见的(不管MD还是MT)
    DllTestStatic2();        // 打印1
    iStatic++;            // exe中的static变量变为了2
    DllTestStatic2();        // 打印1
*/

//------------------------------------------------------------------------------------------

/*
    // 实验4
    // 测试动态链接dll - 静态全局变量是不可见的(不管MD还是MT)
    HMODULE ModuleBase = LoadLibrary(_T("Dll_Static.dll"));
    FuncDllTestStatic2 pDllTestStatic2 = (FuncDllTestStatic2)GetProcAddress(ModuleBase, "DllTestStatic2");
    pDllTestStatic2();        // 打印1
    iStatic++;            // exe中的static变量变为了2
    pDllTestStatic2();        // 打印1
*/

//------------------------------------------------------------------------------------------

    // 实验5
    // 测试dll导出的全局变量,在同一个进程中是可见的
    HMODULE ModuleBase = LoadLibrary(_T("Dll_Static.dll"));
    FuncDllTestStatic2 pDllTestStatic2 = (FuncDllTestStatic2)GetProcAddress(ModuleBase, "DllTestStatic2");
    pDllTestStatic2();        // 打印1
    i++;                // exe中的全局变量变为了2
    pDllTestStatic2();        // 打印2

 

标签:MD,exe,静态,dll,MT,全局变量,Dll,链接
From: https://www.cnblogs.com/renleiguanchashi/p/18087876

相关文章

  • nodejs 实现 AMD 加载 依赖
    importfsfrom"fs";importpathfrom"path";importvmfrom"vm";exportclassLoadComponent{componentsPath:string=path.resolve("../first/components/");componentName:string="";componentIn......
  • AHB_SRAMC_UVMTB
    如何开展验证的工作?拿到设计spec,理解和分析spec写验证计划:验证功能点提取,规划test,tb架构,用什么方法学,功能覆盖率的提取coverpoint,检查机制.....)搭建验证环境(UVMTB)coding编写用例测试,调试debug,发现RTL的bugregression(跑不同的seed)coverage(codefunction)......
  • mdk的基础条叫 && c复习(hal库)
    文章目录11.11.21.31.41.52.c2.12.22.32.42.52.62.72.82.911.1设置了config里面的编码字体颜色用户关键词代码补全动态语法检测配置文件prop在mdk/uv4目录下可以用别人的(和游戏配置复制别人的似的)1.2整体tabshift+tab还有图形快捷键编译速度会变......
  • Windows7系统themeservice.dll文件丢失问题
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个themeservice.dll文件(挑选合适的版本文件)......
  • Windows7系统icfupgd.dll文件丢失问题
    其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个icfupgd.dll文件(挑选合适的版本文件)把它放......
  • md5,aes,ras
    md5加密importhashlib#md5加密defmd5_encrypt(data):#创建一个md5加密器md5=hashlib.md5()#更新加密器的状态,这需要输入的字符串转为为字节串md5.update(data.encode('utf-8'))#获取加密后的16进制字符串returnmd5.hexdigest()if......
  • AMD Zen5越来越近了!Linux GCC编译器已支持
    AMD预计会在今年年中左右开始推出下一代Zen5CPU架构产品,首先从移动端开始,然后是桌面端、服务器端,相关支持也正在紧锣密鼓地进行中,尤其是Linux系统下。现在,AMD已经将Zen5微架构加入到了GCC编译器的支持,GCCGit仓库的target设定值为“znver5”,可以赶上GCC4.1稳定版的发布。目前......
  • 密码加密|jsencrypt|md5|加密解密的两种方式
    一、md5npminstallmd5二、JSEncrypt2.1介绍JSEncrypt属于RSA加密,RSA加密算法是一种非对称加密算法;2.2使用安装:npminstalljsencrypt--dev封装工具:utils/jsencrypt.jsimportJSEncryptfrom'jsencrypt/bin/jsencrypt.min'//密钥对生成http://web.cha......
  • 【Python使用】python高级进阶知识md总结第5篇:获取进程编号,1. 获取进程编号的目的【
    python高级进阶全知识知识笔记总结完整教程(附代码资料)主要内容讲述:操作系统,虚拟机软件,Ubuntu操作系统,Linux内核及发行版,查看目录命令,切换目录命令,绝对路径和相对路径,创建、删除文件及目录命令,复制、移动文件及目录命令,终端命令格式的组成,查看命令帮助。HTTP请求报文,HTTP响应报文......
  • MacBook专业录屏软件Camtasia studio 2024 for mac破解版新功能介绍及安装图文教程
    近年来,随着互联网技术的不断发展,视频制作已经成为人们生活和工作中不可或缺的一部分。作为一款旨在帮助用户创建高质量视频的软件,CamtasiaStudio在这个领域中一直处于领先地位。而随着时代的发展,CamtasiaStudio也在不断更新和完善,为用户提供更好的使用体验。2024年的Camtas......