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