问题描述
主程序去获取算法DLL的结果始出现上述异常,问题简化后关键代码如下:
exe和dll公共TypeDef.h
#pragma once
#include <vector>
using namespace std;
struct AlgoResult
{
bool m_bOK = false;
vector<int> m_vDefectPos;
};
dll里Algo.cpp
#pragma once
#define ALGO_API _declspec(dllexport)
#include "Algo.h"
#include <TypeDef.h>
ALGO_API void GetResult(long long *pData)
{
AlgoResult *pResult = (AlgoResult *)pData;
AlgoResult Result;
Result.m_bOK = false;
Result.m_vDefectPos = {2,5,10};
*pResult = Result;
}
exe获取结果操作
void CUIDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
AlgoResult Result;
GetResult((long long *)&Result);
if (Result.m_bOK)
{
MessageBox(L"OK");
}
else
{
CString mess;
mess.Format(L"NG, 缺陷个数%d", Result.m_vDefectPos.size());
MessageBox(mess);
}
}
出错时堆栈
原因分析
1、exe运行库的链接方式为MD,而dll的链接方式为MT。
“/MT和/MTd表示采用多线程CRT库的静态lib版本。该选项会在编译时将运行时库以静态lib的形式完全嵌入。该选项生成的可执行文件运行时不需要运行时库dll的参加,会获得轻微的性能提升,但最终生成的二进制代码因链入庞大的运行时库实现而变得非常臃肿。当某项目以静态链接库的形式嵌入到多个项目,则可能造成运行时库的内存管理有多份,最终将导致致命的“Invalid Address specified to RtlValidateHeap”问题。另外托管C++和CLI中不再支持/MT和/MTd选项。
/MD和/MDd表示采用多线程CRT库的动态dll版本,会使应用程序使用运行时库特定版本的多线程DLL。链接时将按照传统VC链接dll的方式将运行时库MSVCRxx.DLL的导入库MSVCRT.lib链接,在运行时要求安装了相应版本的VC运行时库可再发行组件包(当然把这些运行时库dll放在应用程序目录下也是可以的)。 因/MD和/MDd方式不会将运行时库链接到可执行文件内部,可有效减少可执行文件尺寸。当多项目以MD方式运作时,其内部会采用同一个堆,内存管理将被简化,跨模块内存管理问题也能得到缓解。
/MD和/MDd将是潮流所趋,/MT和/MTd在非必要时最好也不要采用了。”
2、AlgoResult结构体里含有会发生动态内存变化的m_vDefectPos。在exe里被初始化为空,但在dll里被改变大小,重新分配内存空间。在exe获取结果后释放局部变量Result时,其内部的容器初始的内存已无效,故出现错误。
解决方法
1、尽量将所有项目运行库都以MD链接;
若链接方式更改不了,则可尝试使用下面的方法。
2、使用全局变量传递结果
// AlgoResult Result;
Result.m_bOK = false; //Result已改成全局变量
Result.m_vDefectPos.clear();
3、AlgoResult初始化时就分配好最大内存,避免重新分配内存
struct AlgoResult
{
AlgoResult()
{
m_vDefectPos = vector<int>(100, 0); //一次性分配最大内存
}
bool m_bOK = false;
vector<int> m_vDefectPos;
};
ALGO_API void GetResult(long long *pData)
{
AlgoResult *pResult = (AlgoResult *)pData;
AlgoResult Result;
Result.m_bOK = false;
Result.m_vDefectPos = {2,5,10};
pResult->m_vDefectPos.assign(Result.m_vDefectPos.begin(), Result.m_vDefectPos.end());//内存拷贝避免重分配
}
总结
1、解决方案各项目优先使用默认的MD方式链接运行库,可以减少跨模块内存管理出现问题;
2、如果非用MT方式不可,则可使用全局变量或一次性分配最大内存来避免跨模块内存管理异常;
参考:
a.跨DLL的内存分配释放问题 Heap corruption
b.VS运行时 /MD、/MDd 和 /MT、/MTd之间的区别
c.c++:动态库接口函数返回stl对象的设计原则塈‘__acrt_first_block == header’异常