首页
社区
课程
招聘
[求助]在windows区域在非中文的情况下,字符串如何转换才最安全?
发表于: 2020-11-28 10:33 4691

[求助]在windows区域在非中文的情况下,字符串如何转换才最安全?

2020-11-28 10:33
4691

最近项目中遇到一个奇怪的问题,关于中文字符在系统区域下转换的编码问题。

是这样的,写了一个文件处理函数,示例:


//假设pFile = "C:\\图片.jpg"
bol FileOPT(LPCWSTR pFile)
{
    char m_szFileName[1024] = { 0 };
    WideCharToMultiByte(CP_ACP, 0, pFile, -1, m_szFileName, 1024, NULL, NULL);
    HANDLE hf = CreateFileA(m_szFileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
    fopen(m_szFileName, "rb"); 
    CString strFile = m_szFileName;
    Gdiplus::Bitmap *pBMP = Gdiplus::Bitmap::FromFile(strFile);
}


关于“图片”2字,我用记事本和NotePad++另存出来的值用WinHex看:

  1. ANSI 为:        CD BC C6 AC

  2. UNICODE为: FF FE FE 56 47 72

  3. UTF8为:        EF BB BF E5 9B BE E7 89  87 

从VS的内存监视来看,输入的WCHAR* 确实为FE 56 47 72,与记事本的UNICODE吻合。


WideCharToMultiByte转换过的ANSI记录:

我的机器上是Win10,中文系统,中文区域,非UNICODE区域=中文:

    转换完的数值为:CD BC C6 AC 与纪事本的ANSI吻合

    CreateFileA能打开。fopen也能打开。Gdiplus::Bitmap::FromFile也能打开


我的机器上是Win10,中文系统,英文区域,非UNICODE区域=中文

    同上


我的机器上是Win10,中文系统,中文区域,非UNICODE区域=英文:

    转换的结果:3F 3F,就是显示为2个问号“?”每个中文字1个问号。

    CreateFile失败,fopen失败,

    神奇的是:

        Gdiplus::Bitmap::FromFile(strFile); 成功了

        可能系统内部把它转回UNICODE而且对了,还能访问?

        但是两问号如何能转成正确的中文呢?因为目录下还有别的2个中文字的文件,理论上它们都是2个问号啊。


网上有说把ACP改为936.

但是如果我们是写一个通用的程序呢?就是全世界所有人用电脑都能正常工作的代码,输入可能是任何类型文字,上面这几行业务应该如何写呢?


求教。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 0
支持
分享
最新回复 (17)
雪    币: 2674
活跃值: (2304)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2

GetACP可以获得当前系统的ANSI代码页,不同语言版本的业务软件运行在相应语言的操作系统中。

如果业务软件的非UNICODE编码是gb2312简体中文,就指定常量936。

最后于 2020-11-28 11:42 被低调putchar编辑 ,原因:
2020-11-28 11:28
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
自己用iconv转换
2020-11-28 12:15
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
mb_foyotena 自己用iconv转换
windows的程序,还要用一个linux的库吗?不合适吧,这个应该有基于windows自身api能写出来吧。
2020-11-28 12:43
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
低调putchar GetACP可以获得当前系统的ANSI代码页,不同语言版本的业务软件运行在相应语言的操作系统中。如果业务软件的非UNICODE编码是gb2312简体中文,就指定常量936。
在中文区域,非UNICODE区域=英文(英国)的设置下
GetACP取到的数字是1252,
WideCharToMultiByte使用1252转出来的依然是问号“?”
fopen和CreateFile打开还是失败。

2020-11-28 12:57
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
不是所有的WideCharToMultiByte CP_ACP
都能转换中文,有些编码不支持中文,
windows的文件都是unicode,全部用W结尾的api即可。
2020-11-28 13:42
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
mb_foyotena 不是所有的WideCharToMultiByte CP_ACP 都能转换中文,有些编码不支持中文, windows的文件都是unicode,全部用W结尾的api即可。
上面只是DEMO
真实情况是这样的:
我们用的一些第三方库,当然多数都是开源的,他们基本都是以char*为文件名输入的
我们自己的程序是UNICODE的,但为了传给他们,我基本都得转成char*,转完后结果他们全打不开
就很尴尬了。

我的意思windows平台应该要有相应的办法,将unicode文件名转成本机一定能打开的ansi的文件名。
2020-11-28 14:18
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
sunbinjin 上面只是DEMO 真实情况是这样的: 我们用的一些第三方库,当然多数都是开源的,他们基本都是以char*为文件名输入的 我们自己的程序是UNICODE的,但为了传给他们,我基本都得转成char* ...
ansi不是一种特定的编码,取决于主机设置,有些编码本来就不能表示中文
2020-11-28 15:19
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
mb_foyotena ansi不是一种特定的编码,取决于主机设置,有些编码本来就不能表示中文
这种情况下,ansi当文字显示不正确没有关系,但是否有办法能让系统fopen成功就行。

就好比,在一个纯英文的系统环境,当然区域也肯定是英文的,这种系统下的基于ansi写的exe,就绝对不打开中文文件了吗?或者其它语言的文件。

2020-11-28 16:27
0
雪    币: 477
活跃值: (1412)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
sunbinjin 这种情况下,ansi当文字显示不正确没有关系,但是否有办法能让系统fopen成功就行。 就好比,在一个纯英文的系统环境,当然区域也肯定是英文的,这种系统下的基于ansi写的exe,就绝对不打开中 ...
nt内核全是utf16,所以人家能打开,用的unicode那套api。你转换了ansi,不支持的字符就丢失了,不等价
2020-11-28 17:30
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
11
mb_foyotena nt内核全是utf16,所以人家能打开,用的unicode那套api。你转换了ansi,不支持的字符就丢失了,不等价
嗯,就是说windows没有utf16对ansi文件路径的对应方法。
主要是程序用了一些网上的开源模块,全是char*,要不然我都不用这么麻烦。
我那个基于一个错误的带问号的文件路径,转正unicode后,gdi+尽然能打开也是神奇了。
2020-11-28 17:44
0
雪    币: 2674
活跃值: (2304)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
12
sunbinjin 在中文区域,非UNICODE区域=英文(英国)的设置下 GetACP取到的数字是1252, WideCharToMultiByte使用1252转出来的依然是问号“?” fopen和CreateF ...

GetACP获取的就是你系统中非UNICODE区域设置的代码页,你这样对于中文肯定是乱码,
因为: 最初是简体中文(gb2312,代码页936)解码成unicode,而你通过WideCharToMulitByte再编码成多字节字符集时却使用的是:ANSI Latin 1; Western European(windows-1252,代码页1252)了,编码都对应不上,肯定成乱码了。WideCharToMulitByte时编码也必须一致,即:gb2312编码,代码页936才行。

我的意思是:
1. 软件做成不同语言版本的,每个编码的语言版本在WideCharToMultiByte时指定不同的CodePage,在安装时,GetACP自动判断当前系统所设置的非UNICODE代码页,然后安装对应语言编码的版本,如果遇到不支持的编码就缺省安装英文版
2. 如果软件就只有简体中文版本,在WideCharToMultiByte就指定ACP常量936,编码回来后,任何非UNICODE区域设置为简体中文的windows机器上都能显示简体中文(试了,如果是非UNICODE编码,只能显示非UNICODE区域设置的编码类型)。

如果操作系统跨了语言,一般界面显示时都采用UNICODE字符集。

打开文件不一定非得用: CreateFileA, fopen, fopen_s 可以用对应的UNICODE版本: CreateFileW, _wfopen ,_wfopen_s,

至于GetProcAddress只有ANSI版本,这个不存在问题, 因为系统的API函数名都是合法的C语言标识符,属于ANSI标准字符集,可以正常工作。

最后于 2020-11-29 12:37 被低调putchar编辑 ,原因:
2020-11-28 18:56
0
雪    币: 1469
活跃值: (440)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
fopen在非UNICODE代码页不是中文的情况下,无论如何都不能正常访问路径中带有中文的文件。非要在这种环境下访问中文路径的文件,最好的办法是修改第三方代码。如果你非要纠缠,外挂软件Locale Emulator了解一下
2020-11-28 20:15
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
14
低调putchar sunbinjin 在中文区域,非UNICODE区域=英文(英国)的设置下 GetACP取到的数字是1252, WideCharToMultiByte使用12 ...
嗯,我们的软件确认有中文版和其它语言版本,做成多个版本不现实,维护起来太麻烦了。
现在的软件一般都流行一个exe加一堆语言文件实现多语言,我们也是这么做的。
现在来看,除了把第三方开源模块的接口改掉,非开源模块的应该完全无解了?

对了,我可以get当前显示语言的acp吗?比如系统设置的中文语言,我就get出是中文的id,显示俄文,就get出俄文,这样出错的概率应该小很多。

2020-11-30 09:17
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
vblank fopen在非UNICODE代码页不是中文的情况下,无论如何都不能正常访问路径中带有中文的文件。非要在这种环境下访问中文路径的文件,最好的办法是修改第三方代码。如果你非要纠缠,外挂软件Locale E ...
理解,感觉用户设置成这样,本身就是给了系统一个错误的信号。这种错误配置的情况下,只有全UNICODE程序才能显示正常
2020-11-30 09:55
0
雪    币: 1469
活跃值: (440)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
sunbinjin 嗯,我们的软件确认有中文版和其它语言版本,做成多个版本不现实,维护起来太麻烦了。 现在的软件一般都流行一个exe加一堆语言文件实现多语言,我们也是这么做的。 现在来看,除了把第三方开源模块的接口改 ...
UINT GetACP(void);
返回936就是平时我们用的简体中文。
2020-11-30 15:45
0
雪    币: 174
活跃值: (620)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
17
vblank UINT GetACP(void); 返回936就是平时我们用的简体中文。
控制面板-区域里,非UNICODE里配置为英文,GetACP返回就不是936了。
换言之,在非中文客户手上,如果某人给他一个中文文件,在资源管理器里是能正常显示,打开的,
但在我这种大量用到了ANSI接口模块的程序里,就不能正常打开了。
主要是开源模块大多都用char*。
2020-12-1 09:47
0
雪    币: 27
活跃值: (127)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18

转来转去不罗嗦吗?

  1. fopen 有对应的 Unicode 版本,叫 _wfopen,输入参数就是 Unicode 直接传给它就行了。

  2. 就算用 Windows API 打开文件也可以用 CreateFileW,没必要调用 CreateFileA。

  3. GDI+ 的参数就是 LPCWSTR。你的输入参数刚好符合,非要转成 ANSI 赋值给 CString,然后再传给 GDI+。

不知道做这么多无用功干什么,为什么不能直接点?

bool FileOpen(LPCWSTR pszFile)
{
    FILE *fp = _wfopen(pszFile, L"rb"); 
    Gdiplus::Bitmap *pbmp = Gdiplus::Bitmap::FromFile(pszFile);
}


2021-2-20 16:12
1
游客
登录 | 注册 方可回帖
返回
//