场景
- 在
Windows
上使用VS
开发C++
类的导出dll
时,如果我们的类包含了stl
的模板成员变量,那么编译时会报以下警告. 如何解决?
C4251: “Utils::path_”: class“std::basic_string<_Elem,_Traits,_Ax>”需要有 dll 接口由 class“Utils”的客户端使用
1> with
1> [
1> _Elem=char,
1> _Traits=std::char_traits<char>,
1> _Ax=std::allocator<char>
1> ]
说明
-
导出
C++
的类到dll
时,我们可以在类的class __declspec(dllexport) Utils{}
来声明,之后该类下的所有成员变量和函数都会被导出. 问题来了,stl
模板成员变量(非指针)在导出时会导出在该dll
的编译设置里的C++
对象模型A
,而访问或创建这个类Utils
时,会在调用的模块(非dll
模块)依据这个模块的编译设置创建另一个对象模型B
.A
和B
模型不一致时就会导致两个模块访问不同的模型,从而导致程序崩溃。最简单的模型不一致设置就是Debug
模式和Release
编译出来的模块。还有其他最常见的就是对MSVCR*.dll
的依赖方式,比如项目属性-》配置属性-》常规-》MFC的使用里选静态库中使用MFC
和共享Dll中使用MFC
,两种配置生成的dll
里的C++
对象模型是不同的,往往在Windows
里如果不同模块选择的MFC的使用
不同而导致运行崩溃。所以为了避免不一致,往往编译不同模块都会选择共享Dll中使用MFC
,这样模块可以共享相同的MSVCR*.dll
动态库。
-
还有重要的一点,就是如果有
stl
的模板类作为成员变量,如果想避免出现C4251
警告,那么就把这个成员变量设置为指针变量。当然这个警告可以不理它也可以的,只要保持它的二进制模型一致就行了。比较有效的办法就是在MFC的使用
里选择共享Dll中使用MFC
。 -
另一个在项目属性-》C/C++ ->代码生成-》运行库也可以单独设置,这里会根据上边说的
MFC的使用
设置而联动。所以一般这里不需要动,默认就是选多线程调试 DLL (/MDd)
.
-
为什么我强调是
Windows
?是因为Windows
的C++
二进制模型不兼容。而Linux
和macOS
没这个问题。 -
以下是
glog
库和Gdiplus
库的头文件声明里的也是用的指针变量作为成员变量。
glog/logging.h
- 使用
std::ostringstream *
作为CheckOpMessageBuilder
的成员变量。
class GOOGLE_GLOG_DLL_DECL CheckOpMessageBuilder {
public:
// Inserts "exprtext" and " (" to the stream.
explicit CheckOpMessageBuilder(const char *exprtext);
// Deletes "stream_".
~CheckOpMessageBuilder();
// For inserting the first variable.
std::ostream* ForVar1() { return stream_; }
// For inserting the second variable (adds an intermediate " vs. ").
std::ostream* ForVar2();
// Get the result (inserts the closing ")").
std::string* NewString();
private:
std::ostringstream *stream_;
};
GdiPlusHeaders.h
Gdiplus::Image
类的成员nativeImage
变量也是声明为指针
class Image : public GdiplusBase
{
...
GpImage* nativeImage;
mutable Status lastResult;
mutable Status loadStatus;
private:
Image(IN const Image& C);
Image& operator=(IN const Image& C);
};
例子
- 这里写了一个例子来说明导出
dll
有C++
类的我认为是最佳而且最方便的做法,就是使用成员指针变量。
test-template-class-export.cpp
// test-template-class-export.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include "Utils.h"
using namespace std;
static void TestUtil()
{
Utils u;
auto date = u.GetNowDate();
date.append("-1");
cout << date << endl;
auto t = u.GetNowTime();
if(t.first)
cout << t.second << endl;
char buf[32] = {0};
auto dt = u.GetNowDateWithFormat(buf,sizeof(buf),"%Y-%m-%d %H:%M:%S");
cout << *dt << endl;
delete dt;
auto length = u.path_.size();
cout << length << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "Hello world" << endl;
TestUtil();
getchar();
return 0;
}
exp.h
- 生成
dll
时需要加宏LIBWINDOW_EXPORTS
.
#ifndef __EXP_H
#define __EXP_H
#ifdef LIBWINDOW_EXPORTS
#define LIB_WINDOW __declspec(dllexport)
#elif LIBWINDOW_EXPORTS
#define LIB_WINDOW
#else
#define LIB_WINDOW __declspec(dllimport)
#endif
#endif
Utils.h
#ifndef UTILS_H
#define UTILS_H
#include <string>
#include "exp.h"
#include <utility>
#include <vector>
#include <map>
class LIB_WINDOW Utils
{
public:
Utils();
~Utils();
std::string GetNowDate();
std::string* GetNowDateWithFormat(char* buf,size_t bufSize,
const char* format);
std::pair<bool,std::string> GetNowTime();
std::map<int,std::string>* GetDates();
// 1.这里编译会报 C4251 警告.
std::string path_;
private:
// 1.使用指针避免 C4251 警告.
std::map<int,std::string>* dates_;
};
#endif
Utils.cpp
#include "stdafx.h"
#include "Utils.h"
#include <time.h>
using namespace std;
Utils::Utils()
{
dates_ = new map<int,string>();
}
Utils::~Utils()
{
delete dates_;
}
string Utils::GetNowDate()
{
time_t tt = time(NULL);
auto m_tm = localtime( &tt );
char buf[32] = {0};
strftime(buf,sizeof(buf),"%Y-%m-%d",m_tm);
return buf;
}
string* Utils::GetNowDateWithFormat(char* buf,size_t bufSize,
const char* format)
{
time_t tt = time(NULL);
auto m_tm = localtime( &tt );
strftime(buf,bufSize,format,m_tm);
return new string(buf);
}
pair<bool,string> Utils::GetNowTime()
{
time_t tt = time(NULL);
auto m_tm = localtime( &tt );
char buf[32] = {0};
strftime(buf,sizeof(buf),"%H:%M:%S",m_tm);
return make_pair(true,buf);
}
map<int,string>* Utils::GetDates()
{
return dates_;
}
下载
https://download.csdn.net/download/infoworld/14912526
参考
C/C++ warning C4251: class … 需要有 dll 接口由 class
compiler-warning-level-1-c4251
warning-c4251-when-building-a-dll-that-exports-a-class-containing-an-atlcstrin