文章

重构drawing时遇到的问题及解决方法

重构drawing时遇到的问题及解决方法

此文档来自于企业微信共享文档,大部分是我写的,丛工和高工也有参与。

为了方便自己查阅,把这篇文档重新整理为 md 文档放在这里。

1. “使用标准Windows库”项目中使用CString报错:

项目属性页:配置属性 - 高级 - MFC的使用 - 使用标准Windows库。

包含头文件<atlstr.h>即可,需放在<windows.h>前:

image-20251201103145812

2. 使用大写的“ASSERT();” 报错:

包含头文件<cassert>(见上面的图片),并在业务代码中用小写的assert代替大写的ASSERT即可。

3. CString类型直接赋值给string类型报错:

image-20251201103230290

GetString()GetBuffer()之后搭配宏CT2A都可以,如果获取字符串之后还需修改,建议用GetBuffer(),但是要记得ReleaseBuffer();如果只是为了读取字符串,就用GetString()即可。

4. 讨论:大写的FALSE和TRUE是否有必要用小写的false和true代替

个人认为还是用大写 FALSE 和 TRUE 比较好,宏的修改和定义更为方便,有利于代码移植。

5. strcpy()、strlen()、sscanf()报错:

微软提供了解决不同字符集下兼容性的宏,用微软提供的对应的宏来代替上述函数即可,对于上述的三个函数,分别对应为:_tcscpy()_tcslen()_stscanf()

但是在编译的时候,上述三个函数会有 error + warning报错,分别替换为_tcscpy_s()_tcslen_s()_stscanf_s (),这三个宏对应的函数与上面三个宏对应的函数的区别在于,下面三个函数还多传入了一个参数“字符串长度”。

除了上述三个函数外,还有一些其他函数会报错,也是由于项目从多字节改为 Unicode 导致的,均改为微软提供的宏即可:

image-20251201103325552

建议以后需要用到该类函数时,均使用宏定义的函数,而非直接调用函数。

其他类似函数可以通过上述的几个宏进到 tchar.h 文件中,搜索所需函数对应的宏,来解决不同字符集下报错的问题。

6. 配置依赖项JSON

项目文件夹_depend下增加 json 文件夹。

项目属性:配置属性 -C/C++ -常规 -附加包含目录:

image-20251201103353802

项目属性:配置属性 -链接器 -常规 -附加包含目录:

image-20251201103358597

项目属性:配置属性 -链接器 -输入 -附加依赖项:

image-20251201103404333

另外,附加依赖项可以用修改stdafx.cpp的方法代替修改项目属性:(祁工推荐这个方法)

image-20251201103409030

7. 直接使用char定义的变量传参失败:

用TCHAR代替char,然后TCHAR可以直接赋值给CString类型的变量了,然后就可以调用CString的函数来处理字符串了。

image-20251201103425555

8. goto报错:

报错截图如下:

image-20251201103432330

调整项目属性即可,将 “是(/permissive-)” 改为 “否(/Permissive)” :

image-20251201103439947

9. 提交记录中有很多多余的文件:(编译生成文件的位置没在_Depend下)

image-20251201103456871

该问题咨询了祁工,祁工给出了两种解决方法,并强烈推荐采用第二种方法:

方法1:直接修改项目属性中的输出目录和中间目录,增加“_Out”字节,如下图所示:

image-20251201103507626

方法2:上面的方法确实可以解决问题,但是改完之后,修改目录会认为是后期修改的,所以 “输出目录” 和 “中间目录” 的内容都会被加粗,可能会造成其它的问题。方法 2 就是直接修改项目默认配置,显示起来也不会有粗体文字,就像上面的截图一样。

首先说方法 2 的操作:

找到对应项目中的 PkpmDrawingParaManager.vcxproj 文件,增加项目需要 Import 的配置文件,见下面两张截图中的修改:PKPM Global.prpps 加在前面就可以了,PKPM.props 则需要在每个配置下都增加上。

img

img

上述增加的两个.props位于 solution 的一级目录下,定义了项目的配置属性、事业部统一使用的一些宏。

.props增加进.vcxproj中后,项目加载时会先读取.props中的配置,就相当于修改了项目属性的默认配置。

另外,在 solution 的一级目录中,还存在一个DrawingDesign.props,看文件名也知道这是属于 solution 特有的一些属性配置定义。当需要使用该配置时,与PKPM.props一样,需要在每个配置下都增加对应的Import语句。

当项目属性配置修改后,按照s olution 一级目录中的.gitignore 文件中的定义,提交记录中就会自动忽略_Out_Tmp文件。

10. 当接口函数存在vector参数时:(配置为MT或MTD)

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 这个库。具体会导致的问题此处不做研究。

原文链接:/MD 与 /MT、/MTD与/MDD的区别_假正经的班长的博客-CSDN博客_/md /mt

情况一:vector 参数是引用时。

img

img

vector 临时对象是在调用函数处的 dll1中创建的,但未分配内存。传入函数内,由于 vector 在添加元素,容器尺寸发生变化时,会在 dll2 的堆上动态分配内存,这就导致了当这个临时对象在 dll1 中析构时,会去释放 dll2 在堆上申请的内存,由于程序采用 MTD 形式,各个动态库有独立的堆空间,从而导致内存跨模块释放问题。

解决方法1:各模块均改为 MD/MDD 形式。

解决方法2:在传入函数前,通过 resize 提前在 dll1 堆上申请足够大的空间。

解决方法3:在传入函数前,通过new vector<T>提前在dll1堆上创建一个对象,将指针传入函数。

情况二:vector 参数不是引用时。

由于参数不是引用,传给函数时会复制生成一个临时变量 vector 副本(在 dll1 堆上动态分配内存),再把副本传给函数,当函数结束时副本的生命周期也结束了,副本上的内存就在 dll2 上释放掉了,又造成内存跨模块释放问题。

解决方法1:各模块均改为 MD/MDD 形式。

解决方法2:vector 参数改为引用。

std::string也是存在类似的跨模块释放问题,所以接口参数尽量不要用 string

11. 当接口函数存在CString参数时:

导出类的函数存在以下问题:当两个 dll 配置不同时(一个 Windows 标准库,一个在共享 DLL 中使用 MFC),包含 CString 的接口函数在导出符号表中的命名如下图。

Windows 标准库(ATL)

img

共享 DLL 中使用 MFC(AFX)

img

从图片可以看出由于函数命名不同编译时无法在符号表中定位到函数地址,导致无法解析外部符号错误。

解决方法是:把CSting参数或返回值修改为char[]char*或者LPCTSTR等。

目前虚函数接口还未发现类似问题,可能是由于纯虚类不会生成导出符号表,只会生成续表,调用者通过虚表来定位到函数地址,所以就不存在符号不同的问题。

12. 当接口函数存在TCHAR、CString、LPCTSTR、LPTSTR参数时:

由于 TCHAR 的特殊性,会根据项目配置的字符集选项而自动改变,对项目内部来说提供了便利性,不会根据 unicode 还是多字节配置的改变而反复修改代码。获取便利性的同时还带了一些问题,当有其他项目 dll1 依赖这个动态库 dll2 时,并且 dll1 与 dll2 的字符集配置不同,虽然编译不会报错,但运行时传入的字符串参数是存在问题的。

imgimg

打个比方:dll1 使用 unicode,dll2 使用多字节,当我用 dll1 调用 dll2 的接口函数穿 LPCTSTR,比如说我传个字符串 L"TEST\0",因为依赖的头文件在dll1配置下是要传wchar_t如左图,但在 dll2 配置函数实现时按照 char 规则去读取传入参数如右图,就会造成读到的字符串变为 "T\0",导致传值失败。

解决方法1:项目间统一字符集配置。

解决方法2:不使用 TCHAR 作为接口函数传参,而是 char。

解决方法3:对传入字符串做调整,按照多字节内存形式去构造 unicode,TE 组成 wchar_t第一个字符,ST 组成第二个字符,传入到接口函数。

13. 宽字节与多字节间的转换:

(1)宽字节转多字节:WideCharToMultiByte:

img

wcstombs_s:

img

CW2A:

img

(2)多字节转宽字节:MultiByteToWideChar:

img

mbstowcs_s:

img

CA2W

14. 接口返回的LPCTSTR有时拿不到正确的字符串

有的库的接口函数返回值为LPCTSTR类型,这样是不安全的,因为返回的是一个指针,若是在栈区的指针,生命周期无法保证;若是在堆区的指针,外部还需要记得 delete 掉,因此非常不推荐接口函数的返回类型为LPCTSTR类型,或是CStringstd::string等类型。

接口函数推荐的取字符串的方法为在函数参数中传递一个TCHAR*类型,在接口函数内赋值后再返回。

下方为一个目前推荐的取字符串的接口函数:

1
2
// cName(传出):取到的字符串
extern "C" __declspec(dllexport) void GetColumnName (TCHAR* cName);

15. md的dll依赖mt的静态lib时,关于mfc的一些问题

编译时会出现 mfc140.lib 与 nafxcw.lib 重定义的报错,将 nafxcw.lib 添加到忽略特定依赖项可以编译通过,但是链接时 lib 链接的函数是 mfc140.lib 的函数?,如果 dll 和 lib 的编译的 vs 版本不同时,可能会有问题。

16. UpdateData(FALSE)报错可能原因

img

参考:

线程调用UpdateData函数出错 - Sky_Watcher - 博客园

本文由作者按照 CC BY 4.0 进行授权