資訊技術 – Computer Science

追求寬字元字串和一般字元字串的轉換效率

在 Windows 之中,想將標準字元 (char) 字串轉換成寬字元 (wchar_t) 字串,通常會使用 MultiByteToWideChar() 這個 Win32 API。這個 API 的 prototype 如下:

int MultiByteToWideChar(
    UINT CodePage,              // [in] 說明輸入字串的編碼。本文假設都為 UTF-8。
    DWORD dwFlags,              // [in] 選項旗標。本文不討論。
    LPCSTR lpMultiByteStr,      // [in] 指向輸入字串的指標。
    int cchMultiByte,           // [in] 指定要處理輸入字串 char 個數。-1 表示處理到結尾的空字元。
    LPWSTR lpWideCharStr,       // [out; optional] 輸出的 buffer。
    int cchWideChar             // [in] 描述 lpWideCharStr 的 buffer 大小,單位為 sizeof(wchar_t)。
);

比較有趣的設計是:如果 lpWideCharStr 傳入的值是 nullptr,而 cchWideChar 是 0 的話,這個 API 的回傳值就會是「該字串完成轉換後所需要的 buffer 大小」。這是因為在不同的編碼下每個「文字」的大小可能不一樣;混用各種文字的字串,經過編碼轉換後,所需要的長度並沒有一定的公式可循,必須得真的轉換過一次,才能得知所需的長度 。

由於考慮到記憶體配置與安全性的問題,一般人在使用這個 API 時,會呼叫兩次:第一次取得所需的 buffer 大小、配置適當的記憶體後,再呼叫第二次,把轉換結果填入 buffer。把整個流程寫成函式的話,會長成這個樣子:

Read More

關於 Windows 中程式參數編碼的一些討論

標題實在下得很爛,但不知道怎麼下比較好。

這篇主要是回答 PTT 上 C_CPP 板,有 Windows programming 的初學者在問 C 程式的命令列為什麼不吃 Unicode 的問題。

這個問題其實是很大的。我的回答可能也不是很清楚。但好歹是我花時間寫的東西,就留在這邊記錄一下吧!


首先,我複議 lwecloud(註:另一位鄉民)的說法:都已經 2023 年,Windows 都是 NT-based 的了,不要再用 MBSC/ANSI 了。

你要做的事,不是把 compiler option 改成 MBSC,而是要想為什麼你在 Unicode 下編譯/執行有問題?

Read More

在 VS Code 中給 Rust 專案使用的 launch.json 範例

(更新:rust-analyzer 也有自動產生 launch.json 的功能。可以停留在 main.rs 上,按 F1,選取 "rust-analyzer: Generate launch configuration" 即可。)

留個筆記,不然每次都要靠 google....

{
    // 使用 IntelliSense 以得知可用的屬性。
    // 暫留以檢視現有屬性的描述。
    // 如需詳細資訊,請瀏覽: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type":"lldb",
            "request": "launch",
            "name": "Debug",
            "program": "${workspaceFolder}/target/debug/${workspaceFolderBasename}",
            "args": [],
            "cwd": "${workspaceRoot}",
            "sourceLanguages": [
                "rust"
            ]
        }
    ]
}

然後記得要按 VS Code 自已的 debug/run 按鈕去執行,不要按 main() 上方的 "Run/Debug" inlay。後者不會吃 launch.json 中的 args

參考來源:Setting up a generic launch.json for Rust in VSCode

[中譯] 軟體開發預估的現實

來源:https://twitter.com/svpino/status/1696187613739208878?s=20

自古至今,軟體開發預估一直都是我們用來欺騙自己的謊言之一。

我們都知道這些預估一定不準,但都假裝這些數字多少有些參考價值。等到情況真的爆炸、一發不可收拾的時候,我們才會因此覺得很生氣。

我在大學生的時候,花了很長的時間專注於研究軟體開發預估的方法。

畢業之後,我寫了很多關於這個主題的文章。

接著,我開始在一家公司上班,花了數年的時間研究如何才能讓軟體開發預估更加準確。公司利用我建立的工具產出的軟體,創造了數百萬美元的營業額。

關於這個主題的必讀書目,我沒有漏讀任何一本。Steve McConnel 的著作《軟體預估 (Software Estimation)》我簡直倒背如流。

而我學到最重要的教訓就是:

我們無法預估軟體開發所需的時間或成本。不管是評估者是誰、或是評估者有沒有經驗,都一樣。

要能預估出值得參考的數字,基本上就是一部科幻小說。

而最精采的就是:

他們會要求你給出預估的數字。他們會說:「我們知道這只是預估,難免和現實有出入啦!」,然後保證到時候不會要你負責。

但他們就是會。沒有例外。

這樣的情況有兩種解決方案。第一個方案,我推薦給那些沒有選擇的人:

  1. 把「快速 (quick)」、「單純 (simple)」、「直覺 (straightforward)」、「簡單 (easy)」,以及所有類似的單字從你的字彙中移除。絕對不要用。不能讓別人用這些字眼形容你的工作內容。

  2. 絕對不要主動提供預估的數字。你所說的一切都會變成用來質疑你的證據。

  3. 如果被逼要預估,只預估你知道今天能完成的工作。數字一定要是一個範圍,例如:「這件事要我需要 2 到 4 小時。」

  4. 如果事情你今天做不完,就要用「天」或「週」當成評估的單位。例如:「我應該可以在本週結束之前完成這個功能。」不要用「小時」當單位去評估未來的工作。

但我們都知道,老闆一定會強迫你給出預估的時程。此時應該這麼做:

  1. 先在心裡預估你覺得完成這個任務要花多少時間。

  2. 把這個數字乘以 3。這就是你的預估範圍的下限。

  3. 把下限乘以 2。這就是預估範圍的上限。

舉例來說:如果你覺得某件事情大約一個工作天可以完成,你就要說「大約需要 3 到 6 天」。

然後有趣的是:

你還是沒有辦法準確地在 3 到 6 天中完成。用這個方法估出來的數字,跟其他方法一樣,都是個屁。

所以,這個問題的真正解決方案就是:

去一家完全不管什麼軟體開發預估的公司上班。

[Rust] Unit-Like Struct 在 Embedded Rust 中的應用

在處理嵌入式系統時常會需要控制 GPIO。所謂的 GPIO(General-Purpose I/O),就是 IC 的某些 pin 腳,可以透過軔體設定為「input(偵測外部的訊號是高電位還是低電位)」或是「output(對外輸出高電位或低電位)」狀態。

理論上來說,當 GPIO 設定為 input 狀態時,叫它「輸出高電位」是沒有意義的。反之,設定為 output 時,去讀取 pin 的電位狀態可能也是沒意義的。因此在設計存取 GPIO 的 API 時,最好能有適當的防呆機制。

Read More

[翻譯] Eric 的 BSTR 完全攻略

(原文出處:https://ericlippert.com/2003/09/12/erics-complete-guide-to-bstr-semantics/


如果你曾經用 C 或 C++ 寫過任何使用到 COM 物件的程式,你一定看過類似的程式碼:

STDMETHODIMP CFoo:Bar(BSTR bstrABC)
{ ... }

這個 BSTR 到底是三小?它和 WCHAR* 又有什麼區別?

像 C 或 C++ 這種低階語言,你有絕對的自由可以決定:究竟要用什麼方式實作某種概念。Unicode 字串就是個絕佳範例。用 C++ 來表示長度為 n 個字元的 Unicode 字串的標準方法是一個指向 2 * (n + 1) bytes 記憶體空間的指標。這塊空間中的前 2 * n bytes 是用來表示 UTF-16 編碼字元的無號短整數 (unsigned short integers),最後 2 個 bytes 的內容則是 0,用來表示字串的結尾。

將只有 ASCII 範圍的 wstring 轉換成 string

Windows 的程式,若是 Unicode enabled 的話,預設使用的編碼是 UTF-16,每個字元的型別為 wchar_t。這樣的字串,無法使用 std::string 處理,必須改用 std::wstring。但有時候,現有的函式如果只吃 std::string,而且我們的字串又只包含有 ASCII 定義的字元(也就是只有英文及半型符號)的話,我們就必須把 std::wstring 轉成 std::string

有一種比較偷懶的做法:

std::wstring ws(L"Hello");
std::string s(ws.begin(), ws.end());

但是在比較嚴謹的 C++ 編譯器(例如 VS 2022),就會發出警告(因為隱式把 wchar_t 轉換成 char,可能造成資料流失)。想要避開警告,就必須明確使用 static_cast<> 進行資料轉換。此時就可以利用 std::transform() 來做:

std::wstring ws(L"Hello");
std::string s(ws.size(), 0);
std::transform(ws.begin(), ws.end(), s.begin(), [](wchar_t c){
    return static_cast<char>c;
});

[Rust] 如何產生靜態連結的 Win32/64 執行檔?

在預設情況下,Windows 版本的 rustc 編譯出來的 Win32/Win64 執行檔,會需要一些 Visual C++ 的 DLL 才能執行。對於軟體開發人員來說,在建構開發環境的過程中,這些 DLL 都會被安裝到系統之中,因此不太會遇到編譯出來的執行檔無法執行的問題;但若是我們要將執行檔給別人、在沒有安裝開發環境的電腦上執行,往往就會遇到問題。

我們可以利用 Visual Studio 所內建的工具程式 dumpbin(加上參數 /dependents)來檢查執行檔的 DLL 依存關係:

上面列出來的 DLL 中,KERNEL32.DLLbcrypt.dll 是 Windows 內建的 DLL,其他的都是 Visual C++ 提供的 DLL,不見得每一台 PC 上都會有這些 DLL。

如果希望 rustc 採用靜態連結的方式、移除對 VC++ DLL 的依存性,可以在 Rust 專案中的 .cargo\config 檔案中,加上這兩行:

[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

然後重新編譯程式。這樣就可以了:

  • 資料來源:https://stackoverflow.com/questions/31770604/how-to-generate-statically-linked-executables

在 wxWidgets 中執行繁重工作時不讓 UI 凍結的作法

我想要用 wxWidgets 寫一個程式,能夠掃瞄整個目錄結構,然後把不需要的檔案刪掉。掃瞄這件事情可以用遞迴來做:

void MainFrame::RecursiveClean(const wxString& dirname) {
  wxDir dir(dirname);
  if (!dir.IsOpened()) {
    return;
  }
  wxString name;
  wxFileName fn(dirname, "");
  bool found = dir.GetFirst(&name);
  while (found) {
    wxString fullpath = fn.GetPathWithSep() + name;
    if (wxDirExists(fullpath)) {
      if (delete_folder_patterns_.Match(name)) {
        // ...delete folder...
      } else {
        RecursiveClean(fullpath);
      }
    } else if (wxFileExists(fullpath)) {
      if (delete_file_patterns_.Match(name)) {
        // ...delete file...
      }
    }
    found = dir.GetNext(&name);
  }
}
Code language: C++ (cpp)

但掃瞄的過程很花時間。如果我們就直接這樣硬幹,在掃瞄的過程中,wxWidgets 就沒有辦法處理事件,導致整個 UI 就會停在那邊沒有反應。所以我們要想個方法去解決。

Read More

用 Rust 寫簡單的互動式命令列程式

最近在研究 1904 年日期系統,想說寫一個簡單的 Rust 程式來測試一下想法。主要的轉換函式寫完後,希望把程式寫成簡單的互動式程式,方便測試。

參考網路上的文章,結果如下:

// 先不用管 y1904 的細節,在這個範例中不重要。
mod y1904;

// 使用 std::io::Write 是為了 flush()
use std::io::{stdin, stdout, Write};
use y1904::*;

fn main() {
    loop {
        // 記得轉成 lowercase,比較好處理
        match prompt_for_input("> ").to_ascii_lowercase().as_str() {
            "exit" => break,
            s @ _ => match s.parse::<u64>() {
                Ok(num) => println!("{:?}", num_to_date(num)),
                _ => continue,
            },
        }
    }
}

fn prompt_for_input(prompt: &str) -> String {
    print!("{}", prompt);

    // 如果沒有呼叫 flush() 的話,在使用者輸入之前,上一行可能不會印出來
    stdout().flush().unwrap();

    let mut result = String::new();
    stdin().read_line(&mut result).unwrap();

    // 使用 trim() 去掉換行符號。
    // trim() 回傳的是 &str,因此要再轉成 String 回傳
    result.trim().to_string()
}

至於 y1904 的內容.... 之後再說好了。 XDD