作者closer76 (克楼瑟)
看板C_and_CPP
标题Re: [问题] CreateFile()回传INVALID_HANDLE_VALUE
时间Wed Aug 30 18:37:27 2023
首先,我复议 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
![](https://cache.ptt.cc/c/https/i.imgur.com/3zvpj0nl.png?e=1720030905&s=gxiByKlVaLaOpiFgBYBMwg)
这就是你传入 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
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 223.140.220.215 (台湾)
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_and_CPP/M.1693391849.A.7F2.html
※ 编辑: closer76 (223.140.220.213 台湾), 08/30/2023 18:47:19
※ 编辑: closer76 (223.140.220.213 台湾), 08/30/2023 19:53:49
1F:推 almostreal: 推 好懂 08/30 22:33
2F:推 sarafciel: 很清楚 推一个 08/31 08:55
3F:推 xavier13540: 我後来改_tmain就编译成功了 实际上书上後面的范例程 08/31 19:08
4F:→ xavier13540: 式码也都改用_tmain 可能是10多年前的msvc没有定义 08/31 19:09
5F:→ xavier13540: UNICODE和_UNICODE两个macro(? 08/31 19:09
6F:→ closer76: 以前的msvc的确有可能预设不是 UNICODE 模式,不过应该 08/31 19:28
7F:→ closer76: 可以手动更改设定。 08/31 19:28
8F:推 xam: 其实十几二三十年前的程式书对外行人要入门蛮容易走远路的 09/01 03:48
9F:→ xam: 以前用的写法後来可能不相容或被弃用,但新手看不出这些 09/01 03:50
10F:推 v86861062: 推推推 09/01 12:21
11F:推 lwecloud: 至少WIN32 API这十几年来没什麽变化(? 09/01 14:53
12F:→ lwecloud: 十年前的XCODE或AS的书现在可能连hello world都印不出来 09/01 14:54
13F:推 ctrlbreak: 查了一下Windows SDK手册 98年已经建议大家用unicode了 09/01 15:29
14F:→ xam: 我是觉得有时候一开始旧范例就编不过也未必是坏事,早早跳过去 09/01 20:01
15F:→ xam: 找新的资料还不会浪费时间 09/01 20:01
16F:推 lc85301: 太神啦 09/01 20:41
17F:推 cloki: 我新手这部份真的不好懂 09/02 21:02
18F:→ closer76: 用十几年前的书有时实在是不得已啊!我们公司现在还在用 09/04 11:45
19F:→ closer76: ATL 写 COM 元件....但讲 ATL 最详细的 ATL Internals 09/04 11:46
20F:→ closer76: 最新版已经是2006年出版的,我好不容易买到一本二手花了 09/04 11:48
21F:→ closer76: 好几个月才寄到我手上....orz 09/04 11:48
22F:推 ntps60803orz: 推好心人 09/16 10:08