首先,我复议 lwecloud 的说法:都已经 2023 年,Windows 都是 NT-based 的了,
不要再用 MBSC/ANSI 了。
你要做的事,不是把 compiler option 改成 MBSC,而是要想为什么你在 Unicode 下
编译/执行有问题?
※ 引述《xavier13540 (柊 四千)》之铭言:
> 我最近在用Johnson M. Hart的书学windows的系统程式设计
> 书上给出了这份使用CreateFile()的程式码 简单实作linux上的cp指令
> https://ideone.com/P9q9SD
C++ 标准的 main() prototype 为:
int main(int argc, char* argv[])
依这个 prototype,argv 是一个“char*”的阵列。
其中的每一个 char*,都是一个指向“ANSI 字串”的指标。
这个情况下,argv[1] 代表你的第一个参数 ("a.txt")。
内存示意图如下:
https://i.imgur.com/SgRzyVL.png
右边那些文字字符,一格代表一个 byte (8-bit)。
后面 "???" 的意思是:你不能保证后面是什么。
这可能会引发一些安全性的问题(后面会提到)。
可是你的 main() 宣告变成:
int main(int argc, LPTSTR argv[])
当编译器的设定为 Unicode 时,LPTSTR 最终会被展开为 wchar_t*。
(注:wchar_t 在 Windows 系统中长度为 16-bit;在 Linux 中则是 32-bit)
所以此时,(编译器会认为)argv 是一个“wchar_t*”的阵列。
但是,你的内存分布还是跟前面那张图一样!
这个时候,编译器会认为你的 argv[1] 指向一个由“Unicode 字符”组成的字串。
所以,程式在解读你原本的 char 字串中的每“两个 8-bit 字符”,
组成 16-bit 资料,当成一个 UTF-16 字符来解译!
如果用 VC++ 的 debugger 来观察:
https://i.imgur.com/3zvpj0n.png
这就是你传入 CreateFile() 中的字串。想当然而 "File Not Found"。
而且,除了内容错误外,这个字串还有另一个安全性问题:
wchar_t 字串的结尾也是 '\0',但长度是 16-bit。
所以 ANSI 的 8-bit '\0' 无法结束字串。
可以参考 debugger 那张图的范例,argv[0] 其实还没有结束,
会一直延续到内存有 0x0000 的地方为止!
如果处理不当的话,这个字串可能会存取到不该存取到的内存,造成安全问题。
=======================================
你也许会想:为什么我的 argv 用错型别,但编译器还是给我过?
老实说,我也没有很好的答案。
我只知道 C/C++ 对于 main() 参数的型别检查,一向非常宽松。
那如果我们把 LPTSTR 换回 char* 呢?
我想你也试过了,这样是不行的。
原因在于你想把 argv[0] 传入 CreateFile()。
CreateFile() 其实是个宏,在 Unicode build 下,它会展开为 CreateFileW(),
此时,它的第一个参数为 LPCWSTR,展开后为 const wchar_t*。
你想要把 char* 传进去,编译器不会给过的。
=======================================
那为何选 MBSC 就会过呢?
当你选 MBSC(严格说是“非 Unicode”)的时候,LPTSTR 就会展开为 char*,
而 CreateFile() 也会被展开为 CreateFileA()。
此时的第一个参数就是 LPCSTR,展开为 const char*。
这样就可以过了。执行起来也没有问题。
但是选 MBSC 有什么问题?
第一,你没有办法处理 Unicode 的档名。
我其实不太清楚 Windows 内部的原则,但依据我的实验,
如果你用 char* argv[] 去接参数,
中文的参数会以 Big-5 的编码传到程式中,日文则会是 Shift-JIS。
这样的资料如果不经处理直接传进 CreateFileA() 会不会正确执行?我不敢保证。
第二,现在的 Windows 核心都是用 Unicode 来处理的。
虽然 Windows 提供你 A 版本的 Win32 API,但其实内部也只是帮你转成 Unicode,
再去呼叫 W 的版本。效率一定比较差。
Windows 保留 A 版本只是为了向前相容,非必要不建议再使用了。
=======================================
所以,如果你想处理命令列参数,最好的方法,应该是改用 wmain。
或至少,改用 _tmain,然后编译选项选 Unicode。
(其实如此一来,_tmain 就会被展开成 wmain 了)
如果你坚持使用 main(而且 argv 型别为 char* []),
那其实,你是还可以用 CreateFileA()....... XDDDD