详细教程:如何解决vs 2022 vc++项目 “warning C4819: 该文件包含不能在当前代码页(936)中表示的字符...”

3 篇文章 0 订阅

项目背景

当前在做一个使用opencv dnn模块来推理yolov8的POC, 使用vc code + cmake 管理C++项目,测试阶段在windows上进行,因此默认选择使用MSVC编译器,后续有可能需要发布到Ubuntu来运行推理程序。

问题描述

在vs code中通过cmake tool来生成visual studio c++ 解决方案, 然而在进行vc++项目编译时,
控制台中抛出警告“warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失”,如下图所示:

原因分析过程和解决方案

分析过程

1)先查一下代码页(936)代表什么编码?

代码页(code page)是Windows操作系统的一类字符的编码方案,编号为936的代码页对应的字符编码为GB2312(参见 Code Page Identifiers - Win32 apps | Microsoft Learn


也就是说MSVC使用的默认编码是GB2312。


2)查一下微软站点 VC++ C4819告警的产生原因

关于Visual Studio中抛出的C4819告警,可以阅读“编译器警告(等级 1)C4819”。

里面还提到了一个解决方法。

3)确认一下当前vs code的工程使用是什么字符集

通过“File -> Preferences -> Settings”打开设置界面,搜索“files:encoding”查看当前vs code项目中使用文件编码:

4)确认一下CMake生成的VS 2022的VC++项目使用的字符集

通过查看产生告警的cpp文件所在vc++工程的属性,使用的字符集是,“多字节字符集”。

疑问:这里使用的“多字节字符集”指什么? 是“UTF-8”么?

在microsoft的站点找到一篇关于MFC项目的关于“Unicode 和多字节字符集 (MBCS)支持”文档, 里面有提到,如果项目属性选择的是“多字节字符集 (MBCS)”,表示以特定于区域设置的字符集编码的基于 char 的单字节或双字节字符和字符串。 也就是说它由当前操作系统中使用的区域设置来决定使用的字符编码(查看方式:“控制面板->时钟和区域->区域->管理-》更改系统区域设置...”):

由此可以推断当前VC++项目使用的“多字节字符集”是GB2312,和告警中提到的“代码页(936)”,是一致的。

因此,导致这个告警“warning C4819”的根本原因:  
当前我使用VS Code的来管理CMake项目和进行代码编写,部分源代码中使用了中文注释,VS Code默认使用UTF-8来保存源代码,而通过CMake生成的VS2022的VC++项目的字符编码却是GB2312,这2种编码对中文字符是无法进行兼容的。 

关于“VC++工程属性里的字符集”,可以选择“使用Unicode字符集”或者“使用多字节字符集”,它们之间是互相排斥的。 此选项只控制代码里的API是用宽字符版(即Unicode)的还是ANSI字符版(即GB2312)的,它控制不了源代码中的字符是用Unicode编码还是GB2312编码。

并且这里的“Unicode字符集”指的是UTF-16,而不是“UTF-8”,  “UTF-16” 是需要考虑字节序问题(即高位和低位的问题),而“UTF-8”是单字节编码,它在任何类型的CPU中生成的utf-8序列是一样的,因此不用考虑字节序问题。

5) Unicode与UTF-8,UTF-16,UTF-32的关系

Unicode 是国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换;

Unicode 字符集的编码范围是 0x0000 - 0x10FFFF , 可以容纳一百多万个字符, 每个字符都有一个独一无二的编码,也即每个字符都有一个二进制数值和它对应,这里的二进制数值也叫 码点, 比如:汉字 "中" 的 码点是 0x4E2D, 大写字母 A 的码点是 0x41,具体字符对应的 Unicode 编码可以查询Unicode字符编码表

UTF-8、UTF-16、UTF-32 中的 "UTF" 是 "Unicode Transformation Format" 的缩写,意思是"Unicode 转换格式",可以看作Unicode在计算机中的实现。

UTF-8最少需要8个比特位也就是一个字节来存储,是一种变长字符编码,被定义为将码点编码为 1 至 4 个字节,具体取决于码点数值中有效二进制位的数量。
UTF-16 和 UTF-32 分别需要 2 个字节 和 4 个字节来存储。


6)GB2312字符集

GB2312 把每个汉字都编码成两个字节,第一个字节是高位字节,第二个字节是低位字节。


我们应该如何解决这个问题呢?

解决方案

根据前面的分析和资料查阅,我尝试了以下几种方案:

方案一

一般这个告警都是因为注释中出现了中文字符,可以将源文件里的中文注释的给删了或者修改成英文注释。这是可行的,
但这种做法有些简单粗暴,仅适合用自己编写的源代码或者在只包含少量中文注释的第三方库。

方案二

根据“编译器警告(等级 1)C4819”, 将源文件以Unicode格式保存,操作如下:

在 Visual Studio 中,打开产生告警的源文件,选择“文件-》另存为”。 在“将文件另存为”对话框中,选择“保存”按钮旁的下拉菜单,然后选择“保存时使用编码”。 如果保存到同一文件名,可能需要确认要替换该文件。 在“高级保存选项”对话框中,选择可表示该文件中所有字符的编码(例如,Unicode(带签名的 UTF-8)- Codepage 65001,然后选择“确定”。

不过我目前的源码本身就是以UTF-8格式进行编码的,亲测这种方案并没有解决该MSVC编译器警告问题。

方案三

将系统代码页设置为支持源代码使用的字符集(比如VS Code中使用的是UTF-8)。

设置方法:“控制面板->时钟和区域->区域->管理-》更改系统区域设置...”,勾选Beta 版:使用 Unicode UTF-8 获取全球语言支持,然后要重启一下电脑,使更改生效。


 

注意事项:

尽管目前UTF-8已经成为大部分编程工具使用的默认字符集,这种方案虽然可行,但是有可能产生副作用:  

中文Windows系统默认设置为“中文(简体, 中文)”,编码为"GB2312"。上图中如果勾选红框中的选项,则操作系统的默认编码会设为UTF-8。

操作系统的默认编码一般不要修改,如果默认编码设置错了,在非Unicode程序中会出现乱码。

方案四

方案三是把操作系统级别字符集修改为UTF-8,这个影响面太大,不好评估副作用,我们是否可以在vc++项目级别来解决这个问题呢?

从输出日志来看,这个告警属于编译阶段产生的,我们是否可以通过给MSVC编译器添加某些控制参数来,设置当前项目的源代码的编码格式呢?

经研究和测试,其实我们可以通过给vc项目的的“C/C++”编译器添加 “/source-charset:utf-8” 或者“/utf-8”来达到。

操作如下: 
右键选择有告警的vc项目 -> 打开项目“属性” -> 进入 “C/C++”

更多关于MSVC字符集设置的相关选项说明,可以参考 /utf-8(将源字符集和执行字符集设置为 UTF-8), /execution-charset(设置执行字符集), /source-charset(设置源字符集)

但问题是,目前vc++项目是在vs code中通过CMake来动态生成的,似乎每次手动修改vc++项目的属性也不是很方便?

经测试,其实我们可以通过在项目最上层的CMakeLists.txt中添加下面的代码来动态地给MSVC编译器添加“/source-charset:utf-8”选项:

add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/source-charset:utf-8>") 

 代码解释:通过`$<CXX_COMPILER_ID:MSVC>`判断当前CMake使用的C++编译器是不是MSVC,如果是,在构建vc++项目时,自动添加`/source-charset:utf-8` MSVC 编译项。


修改完之后,重新通过CMake构建代码,你会发现“warning C4819”并没有抛出来了,说明问题解决。

重新打开新生成的VS解决方案,检查对应的vc项目的“C/C++”编译器的“命令行”参数,你会发现 “/source-charset:utf-8”自动添加进去了。

备注:  
如果在vs code中使用CMake生成vs解决方案时,依然抛出“warning C4819”告警,可是尝试,先删除“build”目录,再重新构建。

方案五

安装 Force UTF-8(With No BOM)扩展:  

退出VS2022,将会开始安装该插件:  


安装完成,从新打开VS 2022解决方案,当你创建新的源代码文件时,相关文件会自动采用UTF-8 No Bom进行保存。

如果你需要在VC++项目中使用当前流行UTF-8 No Bom管理源代码,这个插件倒是可以简化操作。但这个只是影响到文件存储的编码格式,并没有解决该MSVC编译器警告问题。

结论

方案一:源代码我们尽可能使用英文注释,这样更有利于将来进行开源。  

方案二和五:只是和文件保存使用的编码格式有关,和MSVC报的该告警没有任何关系。 

方案三:影响整个操系统,不好控制副作用。

方案四:最佳方案,可以很好得控制在vc++项目的级别,不同项目之间不会相互影响,并且结合方案五可以很好得实现VS Code和VC Studio之间的源代码共享。

最终建议

源代码推荐使用使用UTF-8(无BOM) 格式来保存,如果需要通过CMake来生成VC++项目,
可以在CMakeLists.txt中添加下面的语句来保持VC++的项目也使用UTF-8来保存源代码。
编译器中设置编译选项: 

# 必须在add_library , add_executable 前设置,否则无效
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")  # 对于C语言
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>") # 对于C++语言

如果需要在VC Studio添加或者修改源代码,可以安装 Force UTF-8(With No BOM) 插件,把源代码修改自动保存为UTF-8(无BOM)。

  • 41
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
这是一个比较复杂的数据库 包含图书管理 借书还书 学生管理 老师管理 和数据连接的相关应用 代码详细的解释 压缩包里面也有 数据库的文件 代码里设置的数据库 用户是 sa 密码是 123456 使用的时候做相关的修改 下面给出 一部分的代码 继续关注本资源的发布 会后面有很多实用的代码上传 using System.Windows.Forms; namespace LibraryMis { public class DatabaseAccess { /* 声明成员变量,这样这个类的所有方法就可是使用这些变量了 */ private SqlConnection myConnection; private SqlCommand myCommand; private SqlDataAdapter myDataAdapter; private DataSet mySet = new DataSet(); /* 写该类的构造方法,该方法名要跟类名相同,无返回值 * 当new这个类时就会执行这个构造方法 */ public DatabaseAccess() { /* 获得保存连接字符串的文件名及路径 */ //获得应用程序路径 string exePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; //根据路径和文件名构建FileInfo对象 string fileName = exePath + "connectionString.txt"; //建立FileInfo对象 FileInfo f = new FileInfo(fileName); //判断该文件是否存在 if (f.Exists)//文件存在 { //获得文件内容即存在文件的连接字符串 //打开文件,获得读文件的数据流对象 StreamReader sr = f.OpenText(); //读文件到变量 string connectionString = sr.ReadToEnd(); //关闭流 sr.Close(); //由读出的连接字符串创建Connection对象 myConnection = new SqlConnection(connectionString); //由Connection对象创建Command对象 myCommand = myConnection.CreateCommand(); //创建DataAdapter对象 myDataAdapter = new SqlDataAdapter(); myDataAdapter.SelectCommand = myCommand; //创建CommandBuilder对象 SqlCommandBuilder cb = new SqlCommandBuilder(myDataAdapter); //尝试是否能够打开连接 try { myConnection.Open(); } catch (Exception ex) //打开连接出错,可能是连接字符串有问题,这里调用数据库访问设置窗体来重新设置服务器名和数据库名 { MessageBox.Show("连接不到数据库LibraryMis,在“数据库访问设置窗体对数据库访问进行正确的设置”" + ",取消登录后重新启动图书馆管理系统!","警告",MessageBoxButtons.OK,MessageBoxIcon.Warning ); //创建 数据库访问设置窗体,并显示 FormSetDatabase fmsd = new FormSetDatabase(); fmsd.ShowDialog(); } finally { try { myConnection.Close(); } catch (Exception ex) { } } return; } else //文件不存在 { //设置默认的连接字符串 string connectionString = "server=.;database=LibraryMis;uid=sa;pwd=123456"; //把这个字符串写入文件 StreamWriter sw = new StreamWriter(fileName); sw.Write(connectionString); sw.Close(); MessageBox.Show("文件" + fileName + "不存在,已创建该文件重新启动图书馆管理系统","警告",MessageBoxButtons.OK, MessageBoxIcon.Information); return; } } /*创建查询的方法,返回数据集对象DataSet,参数SelectString表示查询的Sql语句,TableName表示要查询的表名*/ public DataSet FillDataSet(string SelectString, string TableName) { myDataAdapter.SelectCommand.CommandText = SelectString;//设置查询的Sql语句 myDataAdapter.Fill(mySet,TableName); return mySet; } /*执行插入,更新,修改的操作,参数CommandString表示Sql语句*/ public void ExeCommand(string CommandString) { myCommand.CommandText = CommandString; myConnection.Open(); try { myCommand.ExecuteNonQuery(); } catch (Exception ex) { MessageBox.Show(ex.ToString(),"警告",MessageBoxButtons.OK,MessageBoxIcon.Warning); } finally { myConnection.Close(); } } /*执行存储过程的方法,参数为Command对象*/ public void ExeStoreProcedure(SqlCommand command) { command.Connection = myConnection; myCommand = command; myConnection.Open(); try { myCommand.ExecuteNonQuery(); } catch (Exception ex) { MessageBox.Show(ex.ToString(), "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); } finally { myConnection.Close(); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老狼IT工作室

你的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值