開發者的奇愛,或者我如何學會停止憂慮並愛上Windows

· · Windows, Windows應用程式開發, 向下相容性, COM, ActiveX, QA, 思想

1. 先說結論

開發 Windows 應用程式時,需要擔心的事情很多。

這台電腦能不能順利啟動。 在沒有系統管理員權限的環境下也能動嗎。 32bit / 64bit 的差異會不會讓程式壞掉。 中文路徑、超長路徑、網路磁碟機、老舊 DLL、COM 元件、ActiveX、印表機、序列通訊、防毒軟體、Windows Update。

一旦開始想,煩惱就沒有盡頭。

但從某個時候開始,我漸漸這麼想。

這與其說是缺點,不如說是 Windows 背負著現實業務走到今天的證據。

Windows 並不是一座乾淨的理想鄉。 但它承載著在現場運作了數十年的軟體、裝置、業務流程,以及人們的使用習慣。

那份混沌,正是 Windows 的有趣之處,也是身為開發者值得去面對的價值所在。

2. Windows 應用程式開發,令人擔心的事很多

在 Windows 應用程式開發中,光靠「程式碼是對的」是不夠的。

在本機環境能動。 在開發者的電腦上能動。 在驗證用的 VM 上也能動。

即便如此,也不代表在客戶的電腦上一定能動。

舉例來說,會發生這樣的事:

  • 找不到 DLL
  • 沒有安裝 Visual C++ 執行階段
  • .NET Framework 版本不符
  • 想從 64bit 處理程序載入 32bit DLL 而失敗
  • COM 註冊已損毀
  • 搞混了 regsvr32.exe 的 32bit 版本和 64bit 版本
  • 假設以目前目錄為前提的程式碼,在工作排程器中壞掉
  • 想寫入 Program Files 底下卻出現權限錯誤
  • 搞錯 AppData\RoamingAppData\Local 的用途
  • 遇到中文使用者名稱或中文資料夾名稱就當掉
  • 超長路徑無法開啟檔案
  • 在網路磁碟機能動,但在 UNC 路徑就不能動
  • 在高 DPI 環境下畫面跑掉
  • 多台顯示器之間座標計算對不準
  • 印表機驅動程式不同,列印結果就不同
  • USB 裝置、相機、量測儀器、序列通訊在某些環境下無法辨識
  • 防毒軟體擋掉部分處理
  • Windows Update 之後行為改變

這樣列出來,會讓人有點頭皮發麻。

如果是網頁應用程式,問題大多被封閉在瀏覽器與伺服器之間;但在 Windows 應用程式裡,問題會擴散到整台電腦。 檔案系統、登錄檔、使用者設定檔、印表機、裝置、網路、安全性原則、安裝程式、執行階段。

與其說應用程式是運行在作業系統之上,不如說它是被放進了整個業務環境之中。

所以 Windows 應用程式開發很難。 不過,把這一切都歸結為「Windows 就是不行」,我覺得有點不太對。

架在上面的,其實是現實世界的複雜性。

3. Windows 是「現場的作業系統」

Windows 並不是只為最新技術而存在的作業系統。

工廠、醫院、政府機關、學校、研究室、廣播、檢測設備、櫃檯接待終端機、報表終端機、倉庫、店鋪、會計、薪資、庫存管理。 在這些地方,至今仍有大量 Windows 電腦在運作。

那裡不是只有最新的網路服務。

有 Excel 巨集。 有 Access。 有老舊的 VB 應用程式。 有 .NET Framework 業務系統。 有用 C++ 寫成的常駐程式。 有 COM 元件。 有 ActiveX。 有 ODBC 連線。 有印表機驅動程式。 有條碼掃描器。 有序列通訊的量測儀器。 有廠商提供的 SDK。

而且,這些東西並不是各自獨立存在的。

人員操作、列印報表、更換 USB 裝置、把 CSV 放進共用資料夾、夜間批次執行、第二天早上業務接續開始。 它們就在這樣一連串的流程裡運作。

Windows 不只是用來啟動應用程式的底座。 它是一個把現場作業、既有資產、周邊設備,乃至人們使用習慣都一併接住的平台。

所以它有時看起來很老舊。 有時很複雜。 有時會讓人疑惑「為什麼這個東西還留著」。

但這也代表,它沒有隨便丟掉那些舊東西。

4. 向下相容性,是一種愛

如果只想讓作業系統看起來美觀,把舊機制切掉會簡單得多。

刪掉舊的 API。 刪掉舊的執行階段。 刪掉舊的設定畫面。 把舊的應用程式當作「本來就不該能動」來處理。

這樣一來,架構或許會變得清爽。 文件或許也會變短。 開發者的心情或許也會輕鬆一些。

但在這背後,會有業務系統因此停止運作。 會有裝置因此停下來。 會有現場因此陷入困境。

Windows 的向下相容性,在技術上相當麻煩。 為了讓 32bit 應用程式能在 64bit Windows 上運作,有 WOW64 這套機制。 在 32bit 與 64bit 之間,登錄檔或 DLL 看到的內容有時會不一樣。 C:\Windows\System32 是 64bit 那一側,而 C:\Windows\SysWOW64 才是 32bit 那一側——這種第一次看到會覺得混亂的結構也存在。

COM 也很麻煩。 光把 DLL 放進去並不會動,有時還需要註冊。 註冊的位置是使用者層級還是機器層級。 是 32bit COM 還是 64bit COM。 需不需要系統管理員權限。

.NET Framework 也是一樣。 現在雖然有 .NET 8 / .NET 9 這類新版 .NET 可以選擇。 但現場至今仍存在 .NET Framework 4.x 的應用程式。 那並不單純是「舊」,而代表著這個版本已經支撐業務多年。

向下相容性,並不是一個純粹美好的概念。 反而是一項沉重的責任。

讓舊東西持續運作,同時也意味著背負著技術債。 但同時,這也是一種不讓使用者的業務停擺的誠實態度。

我認為,Windows 值得被愛的地方,正在這裡。

5. Windows 是一個像地層一樣的作業系統

Windows 裡,堆積著各個時代的技術,就像地層一樣。

Win32。 COM。 ActiveX。 Windows Forms。 WPF。 .NET Framework。 UWP。 WinUI。 Windows App SDK。 MSIX。 PowerShell。 Windows Terminal。 WSL。

每一項技術都有它出現的時代、擅長的領域,以及被使用過的現場。

只看新技術的話,舊技術或許會顯得像是障礙。 但在 Windows 應用程式開發中,經常會遇到無法忽視舊有層次的場合。

舉例來說,即使在開發 WPF 應用程式,列印時也可能碰到 GDI 或印表機驅動程式的問題。 即使用 C# 撰寫程式,若廠商 SDK 是原生 DLL,就會碰到 P/Invoke 或位元數的問題。 即使正在建立新畫面,也可能需要保留既有的 COM 元件或 Excel 整合。

Windows 不是一座已經整理完善的庭園。 反而更接近一座反覆增建改建的巨大城市。

有大馬路。 有地下道。 有老舊的小巷。 也有新建的高樓大廈。

容易迷路。 但在那些小巷深處,至今仍有重要的業務在運作。

開發者需要做的,不是判斷「因為舊了就不需要」。 而是要看清楚,是哪一層的技術,正支撐著哪一項業務。

6. 把麻煩,轉化為設計

標題裡寫的是「停止憂慮」。

但停止憂慮,不代表變得草率。 也不代表對問題視而不見。

停止憂慮,指的是把憂慮轉化為設計。

舉例來說,如果擔心程式能不能啟動,就把啟動確認做成一個 smoke test。

Start-Process .\MyApp.exe

不只是單純確認能不能啟動,也要在沒有設定檔的狀態、沒有記錄檔資料夾的狀態、標準使用者權限、未連上網路、印表機未設定、高 DPI、超長路徑、中文路徑等情境下都試一遍。

如果擔心 DLL,就確認依賴關係。 如果需要 Visual C++ 執行階段,就決定是要包進安裝程式,還是明確寫成前置條件。 如果要用原生 DLL,就不要讓 x86 / x64 / AnyCPU 之間的關係含糊不清。

如果擔心 COM,就把註冊流程寫成操作手冊,並在驗證用電腦上從零重現一次。

# 64bit COM DLL 的註冊範例
C:\Windows\System32\regsvr32.exe .\SomeComponent64.dll

# 32bit COM DLL 的註冊範例
C:\Windows\SysWOW64\regsvr32.exe .\SomeComponent32.dll

如果擔心權限,就避免設計成只有系統管理員權限才能運作。 把使用者設定放進 AppData,整體設定放進 ProgramData。 避免設計成要把記錄檔寫進 Program Files 底下。

如果擔心路徑,就把中文、空白、超長資料夾名稱、UNC 路徑都納入測試。

New-Item -ItemType Directory -Path "C:\測試 用\非常長的資料夾名稱\更深一層的資料夾" -Force

如果擔心 DPI,就在 100%、125%、150%、200% 下確認。 在多台顯示器、且各自倍率不同的情況下,也要確認畫面不會崩壞。 WinForms、WPF、WinUI 各自對 DPI 的處理方式不同,所以要依每種畫面技術分別確認。

如果擔心記憶體或 handle,就使用 Application Verifier、WinDbg、ProcDump、Process Explorer 等工具。 如果擔心當機,就保留事件記錄、傾印檔、應用程式記錄。

Get-EventLog -LogName Application -Newest 20

或者,在較新的環境下,可以像這樣確認事件記錄。

Get-WinEvent -LogName Application -MaxEvents 20

如果擔心在現場無法重現的問題,就提高記錄檔的細緻度。 「發生錯誤」這種訊息是不夠的。 到底想開啟哪個檔案。 到底在尋找哪個裝置。 到底是以哪個使用者執行的。 到底想寫入哪個路徑。 到底哪個 DLL 載入失敗。

把憂慮,轉化成可以被觀察的形式。

這在 Windows 應用程式開發中非常重要。

7. 「在客戶的電腦上能動」才是最終目標

在開發環境能動,很重要。 CI 上測試通過,也很重要。 在乾淨的 VM 上能動,同樣很重要。

但對於 Windows 應用程式來說,最後擋在前面的問題,是「在客戶的電腦上能不能動」。

那台電腦裡,早就裝了各種東西。 可能裝著老舊的印表機驅動程式。 可能裝著自家的資安軟體。 可能已經配置了網路磁碟機。 使用者可能沒有系統管理員權限。 可能因為某些原因無法立即套用 Windows Update。 可能是無法連上網路的環境。

所以,發布方式也變得很重要。

單純交付一個 exe 就好嗎。 要不要做成 MSI。 要不要用 ClickOnce。 要不要做成 MSIX。 能不能用 winget 發布。 要不要放上內部發布系統。 是否需要離線安裝程式。 是否需要程式碼簽署。 是否會被 SmartScreen 或防毒軟體擋下來。

不只是應用程式本身,還要包含安裝、更新、解除安裝、記錄檔蒐集、復原程序,才能真正接近「在現場可以使用」。

在 Windows 應用程式開發中,軟體交付並不是結束。 它需要在客戶的環境裡,今天、明天,持續啟動。

8. 即便如此,我依然愛 Windows 的理由

Windows 有麻煩的地方。 這是事實。

DLL。 COM。 登錄檔。 UAC。 DPI。 印表機。 執行階段。 32bit / 64bit。 網路磁碟機。 超長路徑。 中文環境。 資安軟體。 Windows Update。

要列舉麻煩的東西,可以列出無數個。

但那些麻煩,大多來自於 Windows 長年背負著現實業務的緣故。

不捨棄舊資產,也吸納新技術,同時擁有 GUI 與 CLI,同時承接本機應用程式與網頁,周邊裝置與業務終端機,個人電腦與企業電腦。

那並不是一套美麗的單一思想。

但,很強悍。

而且,身為開發者,這是值得挑戰的事。

要在 Windows 上做出確實能運作的軟體,並不只是呼叫 API 那麼簡單。 而是要讀懂現場的限制,理解既有資產,找出容易出問題的地方,測試、觀察,並在必要的地方修正。

這件事很樸實。 但,有價值。

9. 結語

在 Windows 應用程式開發中,令人擔心的事情永遠不會少。

但那些擔心,大部分也是因為 Windows 這個作業系統,用了很長的時間,接住了現實的業務、現場、裝置,以及人們的操作。

所以我不認為 Windows 只是一個老舊的作業系統。

它是一個一邊與現實妥協、一邊持續運作至今的,龐大、複雜,又有點笨拙的平台。

而在這個平台上做出確實能運作的軟體,至今仍具有很大的價值。

停止憂慮。

但這不代表可以掉以輕心。

而是把憂慮,轉化為設計、測試與觀察。

就這樣,我漸漸愛上了 Windows。

參考連結

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽