Re: [问题] CreateFile()回传INVALID_HANDLE_VALUE

楼主: closer76 (克楼瑟)   2023-08-30 18:37:27
首先,我复议 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
作者: almostreal (Yukai)   2023-08-30 22:33:00
推 好懂
作者: sarafciel (Cattuz)   2023-08-31 08:55:00
很清楚 推一个
作者: xavier13540 (柊 四千)   2023-08-31 19:08:00
我后来改_tmain就编译成功了 实际上书上后面的范例程式码也都改用_tmain 可能是10多年前的msvc没有定义UNICODE和_UNICODE两个macro(?
楼主: closer76 (克楼瑟)   2023-08-31 19:28:00
以前的msvc的确有可能默认不是 UNICODE 模式,不过应该可以手动更改设定。
作者: xam (听说)   2023-09-01 03:48:00
其实十几二三十年前的程式书对外行人要入门蛮容易走远路的以前用的写法后来可能不相容或被弃用,但新手看不出这些
作者: v86861062 (数字人:3)   2023-09-01 12:21:00
推推推
作者: lwecloud (CloudEX)   2023-09-01 14:53:00
至少WIN32 API这十几年来没什么变化(?十年前的XCODE或AS的书现在可能连hello world都印不出来
作者: ctrlbreak   2023-09-01 15:29:00
查了一下Windows SDK手册 98年已经建议大家用unicode了
作者: xam (听说)   2023-09-01 20:01:00
我是觉得有时候一开始旧范例就编不过也未必是坏事,早早跳过去找新的资料还不会浪费时间
作者: lc85301 (pomelocandy)   2023-09-01 20:41:00
太神啦
作者: cloki (夜云天)   2023-09-02 21:02:00
我新手这部份真的不好懂
楼主: closer76 (克楼瑟)   2023-09-04 11:45:00
用十几年前的书有时实在是不得已啊!我们公司现在还在用ATL 写 COM 元件....但讲 ATL 最详细的 ATL Internals最新版已经是2006年出版的,我好不容易买到一本二手花了好几个月才寄到我手上....orz
作者: ntps60803orz (ntps60803)   2023-09-16 10:08:00
推好心人

Links booklink

Contact Us: admin [ a t ] ucptt.com