今日的 Windows 殼層整合 ── 右鍵選單、檔案關聯,以及 Windows 11 的變化
· Go Komura · Windows, 殼層擴充功能, 右鍵選單, 檔案關聯, COM, Windows 11, 檔案總管, MSIX, Windows 開發
更新紀錄(僅初版,2026年08月20日 發布)
- 初次發布
引用本文(DOI(已登錄存檔): 10.5281/zenodo.22176284)
以下 DOI 指向先前登錄的存檔,內容可能與目前正文不同。引用目前正文時,請使用本頁網址。
Go Komura(2026)。〈今日的 Windows 殼層整合 ── 右鍵選單、檔案關聯,以及 Windows 11 的變化〉。小村軟體有限公司。 https://comcomponent.com/zh-TW/blog/windows-shell-integration-context-menu-file-association/
- DOI(已登錄存檔)
- 10.5281/zenodo.22176284
- DOI(上次登錄版本)
- 10.5281/zenodo.22176285
「換成 Windows 11 之後,找不到自家應用程式的右鍵選單了。」實際確認會發現項目並沒有消失,打開選單最後面的「顯示更多選項」就會出現──這類諮詢背後不是故障,而是 Windows 11 對選單所做的設計變更。在現場,它會變成「多按了一次」「找不到項目」這類詢問。
不過,「讓檔案用這個應用程式開啟」和「把自訂命令放進新的右鍵選單」是兩件不同的事。 不先把兩者分開就動手寫殼層擴充功能 DLL,連原本只要註冊關聯就能滿足的需求也會被做複雜。
本文是寫給中小企業 IT 負責人,以及維護業務應用程式的 Windows 開發者的入門文章。從關聯的基礎,講到新舊選單的差異、實作方式的選擇、安裝程式中的註冊與刪除,再到問題的釐清,串成一條線來說明。
1. 先講結論
關聯與 verb 的底層沒有變,Windows 11 變的是選單的呈現方式,分成了新舊兩套。 先決定「想實現什麼」,再只挑出為此必要的機制。
只要「開啟」的話,先從關聯與靜態 verb 考慮
檔案關聯的底層,是登錄檔中「副檔名機碼→ProgID→verb」這樣的三層結構。副檔名機碼指向 ProgID,ProgID 底下的 shell\<verb>\command 保存要啟動的命令列。如果只是想做到「用這個應用程式開啟」,現在依然靠這套註冊與靜態 verb 就夠了,不需要殼層擴充功能 DLL。Microsoft 也建議先從能滿足需求的最單純靜態 verb 開始考慮。12
註冊位置方面,面向全體使用者就寫 HKLM,以使用者為單位就寫 HKCU。HKCR 是把兩者的 Classes 疊起來的合成檢視,所以寫入時要明確指定 HKLM 或 HKCU,把 HKCR 當成查看用。3
另外,登錄成候選,和被選為預設應用程式是兩回事。 按兩下要開啟的預設應用程式由使用者選擇,作業系統會保護這個選擇。安裝程式不該擅自搶走預設值,把自己正確註冊成候選,才是應用程式這一端的工作。4
要把自訂命令放上新選單,就需要 IExplorerCommand 與套件識別
在 Windows 11 上,傳統的 IContextMenu 擴充功能會被移到以「顯示更多選項」(Shift+F10)開啟的舊選單那一側。要把自訂命令放上新選單,就需要實作 IExplorerCommand 並取得套件識別。56
官方路徑是:把實作 IExplorerCommand 的原生 DLL,以 MSIX 資訊清單(desktop4:FileExplorerContextMenus)註冊。若既有應用程式無法全面改成 MSIX,也可以用 sparse package(帶外部位置的 MSIX)只賦予它識別。67
連 DLL 的安全性與解除安裝時的善後一起考慮
傳統殼層擴充功能是會被載入到檔案總管等處理程序內的 COM DLL。擴充功能當掉或變慢,都會波及整個宿主。64 位元的宿主需要 64 位元 DLL,而以受控程式碼實作處理程序內擴充功能並不受支援。89
註冊、變更或刪除關聯之後,要用 SHChangeNotify(SHCNE_ASSOCCHANGED) 送出通知。解除安裝時刪掉自家的 ProgID 等項目、保留副檔名機碼的預設值,是官方的指引。殼層整合不只是把選單做出來,還包括讓變更生效與收拾善後。110
依目的選擇讀法
| 想知道的事、正在發生的症狀 | 該讀哪裡 |
|---|---|
| 該選哪一種實作方式 | 第 6 章的判斷表。先分清楚是只要關聯,還是要在新選單加入自訂命令 |
| 想支援按兩下與「開啟方式」 | 第 2 章的關聯與第 3 章的靜態 verb |
| 明明註冊了卻沒有成為預設應用程式 | 2.4 節的 UserChoice。把候選註冊與使用者的選擇分開看 |
| 在 Windows 11 上選單被藏深了一層 | 第 5 章的新舊選單。做法見 5.2 節,無法改成 MSIX 時見 5.3 節 |
| 安裝程式該註冊與解除什麼 | 第 7 章。特別是 7.3 節以使用者為單位的註冊與 7.4 節的善後 |
| 選單不出現、出現兩次、檔案總管變慢 | 第 8 章的釐清步驟。DLL 的限制見第 4 章 |
想從機制學起就從第 2 章依序讀;要決定既有應用程式的因應方針,也可以從第 6 章開始讀。
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 16 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
2. 檔案關聯怎麼運作 ── 副檔名機碼→ProgID→verb 的三層結構
這一章依照檔案側的註冊 → 寫入位置 → 應用程式側的註冊 → 使用者的選擇的順序來整理。副檔名指向 ProgID、verb 保存命令列,較複雜的擴充功能則以處理程序內 COM DLL 的形式運作──這套底層二十多年來沒有變過。
2.1. 用一個例子讀懂三層結構
先用一個例子看關聯的基本形。副檔名機碼指向 ProgID,ProgID 裡的 verb 則保存要啟動的命令。1 當使用者已經選了預設應用程式時的優先關係,會在 2.4 節說明。
HKEY_CLASSES_ROOT
.kmrpt ← (1) 副檔名機碼
(Default) = KomuraSoft.Report.1 ← 只是指向 ProgID 的指標
OpenWithProgids
KomuraSoft.Report.1 ← 「開啟方式」的候選
KomuraSoft.Report.1 ← (2) ProgID(關聯的實體)
(Default) = 小村報表文件
DefaultIcon
(Default) = "C:\Program Files\KomuraSoft\Report.exe",0
shell ← (3) verb(動詞)清單
open
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
- (1) 副檔名機碼(
.kmrpt)只是用預設值指向 ProgID 的名稱。在這裡直接寫命令是錯的。 - (2) ProgID(
KomuraSoft.Report.1)才是關聯的實體,持有顯示名稱、圖示與 verb 清單。 - (3) verb 是「開啟」「列印」這類動詞,
shell\open\command的預設值就是實際被啟動的命令列。
有了這樣的分離,就能把多個副檔名(例如 .kmrpt 與 .kmrpt-file)指向同一個 ProgID,也能在應用程式升版時替換 ProgID。
flowchart TB
accTitle: 檔案關聯的三層結構
accDescr: 副檔名鍵是預設值指名 ProgID 的指標;ProgID 是儲存顯示名稱、圖示與 verb 列表的實體;verb 底下 command 的預設值才是實際啟動的命令列
ext["副檔名鍵 .kmrpt"] -->|預設值指名 ProgID| pid["ProgID KomuraSoft.Report.1"]
pid --> vb["verb(shell 底下的 open 等)"]
vb --> cmd["command 預設值"]
cmd --> exe["啟動 Report.exe"]
pid -.-> attr["也儲存顯示名稱與 DefaultIcon"]
圖 1: 副檔名機碼是指標,ProgID 是實體,verb 的 command 才是實際啟動的命令列。
2.2. HKCR 是「合成檢視」── 寫到哪裡,意義就不同
上面的例子是用 HKEY_CLASSES_ROOT(HKCR)表示的,但HKCR 並不是實體的儲存位置,而是把 HKLM\Software\Classes 與 HKCU\Software\Classes 疊起來的合成檢視。同一個機碼若兩邊都有,HKCU 這邊獲勝。3
flowchart TB
accTitle: HKCR 是合併檢視
accDescr: HKCR 是把 HKLM 與 HKCU 的 Classes 疊在一起;兩邊有同一個鍵時 HKCU 獲勝;註冊要明確寫到 HKLM 或 HKCU,把 HKCR 當唯讀
hklm["HKLM\\Software\\Classes(全體使用者)"] --> hkcr["HKCR(合併檢視)"]
hkcu["HKCU\\Software\\Classes(單使用者)"] --> hkcr
hkcu -.-> win["同一鍵存在時 HKCU 獲勝"]
hkcr -.-> ro["當唯讀(用來確認)"]
圖 2: HKCR 是 HKLM 與 HKCU Classes 疊在一起的樣子;寫入目標一律指明其中一邊。
| 寫入位置 | 意義 | 需要的權限 |
|---|---|---|
HKLM\Software\Classes |
全體使用者共用的註冊 | 系統管理員權限 |
HKCU\Software\Classes |
只屬於該使用者的註冊 | 不需要 |
直接寫入 HKCR |
依既有機碼所在的位置決定寫到哪一邊 | 視情況而定 |
實務上,安全的做法是一律把註冊明確寫到 HKLM 或 HKCU 其中一邊,並把 HKCR 當成讀取(確認)用。
不要把關聯資料與 COM 註冊的位元數混為一談
在 WOW64 的登錄檔重新導向裡,關聯資料與 COM 註冊的處理方式並不相同。副檔名機碼、ProgID 這類位於 HKLM\Software\Classes 正下方的關聯資料,自 Windows 7 起在 32 位元/64 位元的登錄檔檢視之間是共用的,所以就算由 32 位元安裝程式寫入,也不會跑到 Wow6432Node 那一側。
另一方面,Classes\CLSID 等 COM 註冊相關的部分子機碼則屬於重新導向對象,在後面會談到的殼層擴充功能(處理程序內 COM)註冊上,32 位元/64 位元分開寫就有了意義。細節在「登錄檔的 32bit/64bit 重新導向與虛擬化的陷阱 ── Wow6432Node 與「明明寫入了卻讀不到值」的問題」中討論。
2.3. 應用程式側的註冊 ── App Paths、Applications、RegisteredApplications
與檔案側(副檔名與 ProgID)成對的應用程式側註冊,也有三種。11
- App Paths(
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths):讓ShellExecuteEx只用執行檔名稱就能啟動的註冊。因為不必污染 PATH 環境變數,Microsoft 建議採用這一種。 - Applications(
HKCR\Applications\<應用程式.exe>):定義以「開啟方式」被交付任意檔案時的預設開啟方式,以及應用程式的顯示名稱(FriendlyAppName)。 - RegisteredApplications + Capabilities:宣告應用程式能處理的副檔名與 MIME 類型,好讓它以候選身分出現在 Windows 的預設應用程式設定畫面。
「我們的應用程式沒有出現在預設應用程式清單裡」這類諮詢,大多是註冊了 ProgID 卻省略了這個 Capabilities 註冊。
flowchart TB
accTitle: 應用端的三種註冊
accDescr: 應用端註冊有 App Paths、Applications、RegisteredApplications 三種,分別負責只靠檔名啟動、開啟方式的預設開法,以及出現在預設應用設定頁
app["應用端註冊"] --> ap["App Paths"]
app --> apps["Applications"]
app --> ra["RegisteredApplications"]
ap --> r1["只靠檔名啟動"]
apps --> r2["開啟方式的預設"]
ra --> r3["出現在預設應用頁"]
r3 -.-> cap["需要 Capabilities 宣告"]
圖 3: 應用端註冊有三種,要成為預設應用候選需要 Capabilities 註冊。
2.4. 預設應用程式屬於使用者 ── UserChoice 的保護
就算在副檔名機碼的預設值裡寫上 ProgID,也不一定就會成為預設應用程式。使用者透過「開啟方式」等方式明確選擇的結果,會保存在 HKCU\...\Explorer\FileExts\<副檔名>\UserChoice,而關聯的解析會以這邊為優先。
不要把設計做成直接改寫 UserChoice
Windows 並不支援由程式變更預設應用程式。 預設應用程式的設定被設計成由使用者透過系統設定 UI 完成,UserChoice 的資料經過混淆處理,篩選器驅動程式(UCPD.sys)會阻擋應用程式寫入。在受管理的環境中,群組原則/MDM 原則才是官方手段。4
過去之所以會出現 SetUserFTA 這類「模仿雜湊值再改寫」的工具,正是這道保護的反面。
安裝程式要做的是「準備好被選中」
要放進自家應用程式安裝程式的,是下面三件事。
- 正確註冊 ProgID 與 verb。
- 把自己加進 OpenWithProgIds。
- 必要時把使用者引導到預設應用程式的設定畫面。
不是搶走預設值,而是把使用者可以選擇的狀態準備好。
flowchart TB
accTitle: 預設應用解析與 UserChoice 保護
accDescr: 使用者明確選擇的結果儲存在 UserChoice 並在關聯解析中優先;UCPD.sys 阻止應用改寫,因此安裝程式能做的是註冊成候選並引導到設定頁
uc["UserChoice(使用者的選擇)"] -->|優先| res["關聯解析"]
ext["副檔名鍵預設值"] --> res
wr["從應用改寫"] -.->|UCPD.sys 阻止| uc
res ~~~ inst["安裝程式的工作"]
inst --> a1["註冊 ProgID 與 verb"]
inst --> a2["加到 OpenWithProgIds"]
inst --> a3["引導到設定頁"]
圖 4: 關聯解析優先採用使用者的選擇(UserChoice),作業系統保護它不被應用改寫。
3. 「開啟」以外的 verb ── print、edit、runas 與自訂 verb
3.1. 標準 verb 與自訂 verb
verb 不只有 open。作業系統知道其含意的標準 verb,除了 open 之外還有 edit、print、play、preview 等,而標準 verb 會依作業系統的地區設定自動取得顯示名稱。按兩下時使用的預設 verb,是依 shell 機碼的預設值→登錄檔上的第一個 verb→open→openwith 的順序決定。12
flowchart TB
accTitle: 預設 verb 的決定順序
accDescr: 按兩下使用的預設 verb,是依 shell 鍵預設值、登錄檔中第一個 verb、open、openwith 的順序找到的第一個
s1["shell 鍵預設值"] -->|若無| s2["登錄檔中第一個 verb"]
s2 -->|若無| s3["open"]
s3 -->|若無| s4["openwith"]
圖 5: 按兩下時的預設 verb,是依此順序找到的第一個。
想加上自己的動詞時,就註冊自訂 verb。
KomuraSoft.Report.1
shell
open
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" "%1"
print
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" /print "%1"
verify ← 自訂 verb
(Default) = 驗證報表(&V) ← 選單上的顯示名稱
command
(Default) = "C:\Program Files\KomuraSoft\Report.exe" /verify "%1"
3.2. 分清楚提升權限、按住 Shift 時的顯示,以及老舊的 DDE
verb 還有下面這些指定方式。
- 註冊名為 runas 的 verb,就能定義相當於「以系統管理員身分執行」的提升權限啟動,
ShellExecute系列 API 指定runas的啟動也會用到它。 - 在 verb 機碼上放一個名為
Extended的空值,它就會變成只有按住 Shift 鍵再按右鍵時才會顯示的擴充 verb。用來把很少使用的危險操作藏起來很方便。12 - 舊應用程式的關聯裡,有時還留著用 DDE(
ddeexec機碼)把文件送進既有處理程序的組態,但以 DDE 啟動 verb 已經是不建議使用(Deprecated)的遺產。沒有理由在新的實作裡寫它。12
3.3. 用引號把 EXE 路徑與所選檔案的路徑括起來
最常出問題的是命令列的引號。只要命令字串的元素可能含有空格,就一定要用引號括起來。C:\Program Files\... 這類 EXE 路徑自不用說,%1(所選檔案的路徑)也應該一律寫成 "%1"。因為沒辦法保證使用者的檔案路徑不含空格。沒有引號的 My Program.exe 會被解釋成「啟動 My,並帶上 Program.exe 這個引數」。13
flowchart TB
accTitle: 命令列的引號事故
accDescr: 沒加引號的命令會在空格處被切開,誤當成啟動 My 並帶引數 Program.exe,因此可能含空格的 EXE 路徑、以及代表所選檔案路徑的 %1,都應一律用引號包起來
c1["沒加引號的命令"] -->|在空格處切開| bad["被誤當成啟動別的 EXE"]
c2["有引號的命令"] --> good["按預期啟動"]
c2 -.-> q1["用引號包住 EXE 路徑"]
q1 -.-> q2["%1 也一律用引號包住"]
圖 6: 沒加引號的命令會在空格處被錯誤切開,因此 EXE 路徑與 %1 都要一律用引號包住。
3.4. 動手寫 DLL 之前,先確認靜態 verb 是否就夠
到這裡為止只靠登錄檔的機制(靜態 verb),一個 DLL 都不用寫就能實現,也沒有讓檔案總管變得不穩定的風險。Microsoft 自己就一再表示:「在撰寫殼層擴充功能之前,先評估能否用滿足需求的最單純靜態 verb 解決。」2
4. 傳統殼層擴充功能 ── 在檔案總管裡面執行的 DLL
這一章要掌握的重點是:傳統的擴充功能 DLL 是在檔案總管等處理程序內執行的。 理解了它執行的位置,穩定性、位元數與實作語言這些限制就能串起來。
4.1. 殼層擴充功能的種類
「依所選內容動態改變選單」「替換圖示或內容(屬性)畫面」這類靜態 verb 做不到的需求,就要用殼層擴充處理常式。常見的種類如下。8
| 處理常式 | 主要介面 | 能做什麼 |
|---|---|---|
| 內容功能表處理常式 | IContextMenu + IShellExtInit | 動態新增與控制選單項目 |
| 圖示處理常式/圖示覆疊 | IExtractIcon / IShellIconOverlayIdentifier | 每個檔案的圖示與疊加顯示 |
| 屬性頁處理常式 | IShellPropSheetExt | 在內容畫面加入索引標籤 |
| 縮圖/資訊提示 | IThumbnailProvider / IQueryInfo | 縮圖顯示與滑鼠停留時的說明 |
| 拖放/複製攔截處理常式 | IDropTarget / ICopyHook | 在放下、複製與移動時介入 |
這些全都以 COM 類別實作,並把 CLSID 註冊到登錄檔。COM 本身的觀念請參閱「COM / ActiveX / OCX 是什麼 - 差異與關係一次整理」。
4.2. 身為處理程序內 COM 伺服器代表什麼
傳統殼層擴充功能的本質,是會載入到檔案總管(以及任何開啟過通用檔案對話方塊的應用程式)內部執行的處理程序內 COM 伺服器(DLL)。所有注意事項都由此導出。8
- 擴充功能一旦當掉,檔案總管會被連累一起當掉。若是卡住,按右鍵就會停頓好幾秒。而且受害的不只檔案總管,還遍及所有顯示過開啟檔案對話方塊的應用程式。
- 選單的建構是在 UI 執行緒上進行的,所以不可以在顯示選單時做網路存取或檔案 I/O 這類慢速處理。
- 執行緒模型原則上要註冊為
Apartment。
flowchart TB
accTitle: 處理程序內擴充功能的連帶損害結構
accDescr: 殼層擴充功能 DLL 不只載入到檔案總管,也載入到開啟檔案對話方塊的任何應用程序,因此擴充功能的崩潰或掛住會波及整個宿主程序
dll["殼層擴充功能 DLL"] -->|處理程序內載入| exp["檔案總管"]
dll -->|處理程序內載入| any["開啟對話方塊的任何應用"]
exp --> dmg["崩潰或掛住會擴散"]
any --> dmg
dmg -.-> rule["顯示時不要做慢工作"]
圖 7: 擴充功能 DLL 在宿主處理程序中執行,當掉或卡住都會波及整個宿主。
調查「打開某個資料夾檔案總管就卡住」「按右鍵要等五秒」這類諮詢時,原因不是自家應用程式而是第三方的殼層擴充功能,其實並不罕見。縮小範圍的做法會在第 8 章說明。
4.3. 位元數要一致 ── 64 位元環境必須用 64 位元 DLL
處理程序內 DLL 的位元數必須和載入它的處理程序一致。64 位元 Windows 的檔案總管是 64 位元處理程序,所以只建置成 32 位元的殼層擴充功能 DLL 根本不會被載入,也完全不會出現在選單上。而且不會有任何錯誤訊息,這是「明明註冊了卻沒出現」的經典原因。
這不代表連應用程式本體都得改成 64 位元
32 位元的應用程式本體搭配 64 位元的殼層擴充功能 DLL 是正當的組態,但要注意 COM 註冊會依位元數分開(Wow6432Node)。另外,從 verb 的 command 啟動的是另一個處理程序的 EXE,因此不受這項限制(維持 32 位元 EXE 也沒問題)。
flowchart TB
accTitle: 殼層擴充功能 DLL 的位元數對齊
accDescr: 64 位檔案總管能載入的殼層擴充功能 DLL 只有 64 位;僅 32 位的 DLL 不會出現在選單上也沒有錯誤;從 verb command 啟動的 EXE 是獨立程序,不受此限制
exp["64 位檔案總管"] -->|可載入| d64["64 位殼層擴充功能 DLL"]
exp -.->|無法載入| d32["僅 32 位的 DLL"]
d32 -.-> sym["沒有錯誤、選單上不出現"]
exe["從 verb 啟動的 EXE"] -->|獨立程序| ok32["維持 32 位沒問題"]
圖 8: 能被 64 位元檔案總管載入的只有 64 位元 DLL,由 verb 啟動的 EXE 不受此限制。
4.4. 不該用受控程式碼撰寫的理由
我們常被問到「能不能用 C# 寫殼層擴充功能」,但Microsoft 明確表示:以受控程式碼(.NET)撰寫處理程序內的殼層擴充功能並不建議,也不在支援範圍內。9
理由在於擴充功能會被載入任意處理程序這項性質。讓宿主應用程式變得不穩定的因素,主要有下面三個。
- CLR 的版本衝突。 尤其在 .NET Framework 4 以前會出問題。
- 重入。 等待鎖時,CLR 會重入訊息迴圈,這是個問題。
- 物件存留期。 記憶體回收造成的存留期非決定性,會與 COM 的參考計數約定衝突。
其中有些項目在 .NET Framework 4 之後與現代的 .NET 上已經緩解,但官方立場並沒有改變。
實務上的方針很簡單。處理程序內擴充功能用原生 C++ 撰寫;想用受控程式碼,就做成由 verb 的 command 啟動的一般 EXE,或是在另一個處理程序中執行的處理程序外擴充功能(預覽處理常式等)。9
flowchart TB
accTitle: 判斷託管程式碼是否允許
accDescr: 在檔案總管內執行的處理程序內擴充功能原則上用原生 C++ 編寫;若要用託管程式碼,就做成從 verb command 啟動的普通 EXE,或在獨立程序執行的程序外擴充功能
q1{"在處理程序內執行?"} -->|是| cpp["用原生 C++ 寫"]
q1 -->|否| mg["託管程式碼沒問題"]
cpp -.-> why["CLR/重入風險使宿主變得不穩定"]
mg --> e1["由 verb 啟動的 EXE"]
mg --> e2["程序外預覽"]
圖 9: 處理程序內擴充功能原則上用原生 C++,受控程式碼只用在另一個處理程序執行的組態。
5. Windows 11 的新內容功能表 ── 選單變成兩層
這裡依照被移到舊選單的原因 → 註冊到新選單 → 保留既有安裝程式的做法的順序說明。只是「用這個應用程式開啟」的關聯,和新增自訂命令之間的差別,會在 5.4 節確認。
5.1. 發生了什麼事
Windows 11 改版了檔案總管的右鍵選單。剪下、複製等變成上方的圖示列,「開啟」「開啟方式」被集中放在上方,應用程式新增的命令則被歸類到殼層標準命令的下方。同一個應用程式若新增多個命令,會收進帶有應用程式名稱的飛出視窗(子選單)。5
而最關鍵的是這一點:傳統以 IContextMenu 為基礎的殼層擴充功能並沒有被移除,而是被移到用「顯示更多選項」(Shift+F10)開啟、原封不動載入 Windows 10 選單的舊選單那一側。5 開頭那則諮詢裡「選單被藏起來了」的真正原因,就是這種兩層化。
flowchart TB
accTitle: Windows 11 拆成兩層的右鍵選單
accDescr: 右鍵先開啟的是新選單;能出現在那裡的命令只有以 IExplorerCommand 與套件識別註冊的項;傳統 IContextMenu 擴充功能被移到用顯示更多選項開啟的舊選單
rc["對檔案右鍵單擊"] --> newm["新選單(Windows 11)"]
newm --> newi["IExplorerCommand + 識別的命令"]
newm -->|顯示更多選項 Shift+F10| oldm["舊選單(Windows 10 選單)"]
oldm --> oldi["傳統 IContextMenu 擴充功能"]
newi -.-> fly["多個命令收進浮出"]
圖 10: 新選單上只放 IExplorerCommand + 識別的命令,傳統擴充功能被移到舊選單那一側。
5.2. 放上新選單的官方路徑 ── IExplorerCommand + 資訊清單註冊
要把自訂命令放上新選單,官方路徑是實作 IExplorerCommand 的原生 DLL,加上以 MSIX 資訊清單註冊。6
資訊清單裡要寫兩種宣告
下面的例子,前半宣告COM 伺服器(CLSID 與實作 DLL),後半宣告內容功能表擴充功能(對象與命令)。把兩者綁在一起的,是同一個 CLSID。
<!-- 套件資訊清單(節錄) -->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:SurrogateServer DisplayName="Komura commands">
<com:Class Id="01234567-89AB-CDEF-0123-456789ABCDEF"
Path="KomuraCommand.dll" ThreadingModel="STA" />
</com:SurrogateServer>
</com:ComServer>
</com:Extension>
<desktop4:Extension Category="windows.fileExplorerContextMenus">
<desktop4:FileExplorerContextMenus>
<desktop5:ItemType Type=".kmrpt">
<desktop5:Verb Id="VerifyReport"
Clsid="01234567-89AB-CDEF-0123-456789ABCDEF" />
</desktop5:ItemType>
</desktop4:FileExplorerContextMenus>
</desktop4:Extension>
ItemType 的 Type 除了特定副檔名之外,還可以指定 *(所有檔案)、Directory(資料夾)與 Directory\Background(資料夾背景)。DLL 要和檔案總管的架構(64 位元/ARM64)一致。6
建構選單的方法要快速返回
IExplorerCommand 本身是從 Windows 7 時代就有的介面,要實作標題(GetTitle)、圖示(GetIcon)、啟用/停用/隱藏的狀態(GetState)與執行(Invoke)。方法是從 UI 執行緒呼叫的,因此禁止存取網路資源,建構選單這類方法必須快速返回。繁重的處理要放到 Invoke 之後再做。146
flowchart TB
accTitle: 新選單註冊的清單結構
accDescr: MSIX 清單的 COM 伺服器宣告把 CLSID 對映到 DLL,右鍵選單擴充功能宣告用 ItemType 與 Verb 把目標與實現綁在一起,於是自訂命令出現在新選單上
man["MSIX 清單"] --> com["COM 伺服器宣告"]
man --> ctx["選單擴充功能宣告"]
com -->|把 CLSID 對映到 DLL| impl["IExplorerCommand 實現 DLL"]
ctx -->|用 ItemType 與 Verb 指定| impl
impl --> shown["命令出現在新選單"]
ctx -.-> tgt["目標是副檔名、所有檔案等"]
圖 11: 資訊清單裡的兩個宣告把實作 DLL 與對象綁在一起,命令才會出現在新選單上。
5.3. 未封裝應用程式的選項 ── 用 sparse package 只取得識別
「我們的應用程式只能用 MSI 發布,改成 MSIX 不可行」時的退路,就是 sparse package(帶外部位置的 MSIX)。做一個不含應用程式本體、只有資訊清單的小型 MSIX 並加以簽章,在既有安裝程式的最後註冊它。這樣應用程式就取得了套件識別,也就能進行上述的資訊清單註冊(=顯示在新選單上)。
可用的版本是 Windows 10 版本 2004 以後,而且套件需要以目標電腦所信任的憑證簽章。7 註冊與解除的順序,以及「它是以使用者為單位註冊」這件事該注意什麼,會在 7.3 節說明。
flowchart TB
accTitle: 用 sparse package 取得識別的流程
accDescr: 現有安裝程式放好應用本體後,以外部位置註冊只有清單的 sparse package,應用就取得套件識別,並能做新選單的清單註冊
inst["現有安裝程式"] --> files["放置應用本體"]
sp["sparse package"] -.-> only["只有清單,沒有本體"]
files --> reg["以外部位置註冊"]
sp --> reg
reg --> id["取得套件識別"]
id --> ok["新選單註冊成為可能"]
sp -.-> sign["需要受信任的簽章"]
圖 12: 把不含本體的 sparse package 以外部位置註冊後,應用程式就取得套件識別。
最大的優點是不必替換安裝程式,對於握有既有 MSI/EXE 安裝程式資產的應用程式來說,這是務實的解法。與全面移轉到 MSIX 的比較,也可參閱「Windows 應用發布方式怎麼選 - MSI / MSIX / ClickOnce / xcopy / 自訂 updater 的判斷表」。
5.4. 關聯的 verb 在新選單上長什麼樣
這點很容易被誤解:第 2〜3 章的關聯(ProgID 與 verb)在新選單上依然有效。 按兩下的預設 verb、「開啟」與「開啟方式」的候選,都是從關聯解析出來的,並顯示在新選單的上方。也就是說,若只是「想讓檔案能用這個應用程式開啟」,在 Windows 11 上不需要任何額外處理。
另一方面,關聯並不是通用的選單擴充機制,所以想把任意自訂命令放到新選單的第一層,就需要 IExplorerCommand 加上套件識別——這就是兩者的分工。6
flowchart TB
accTitle: 關聯與新選單的角色分工
accDescr: ProgID 與 verb 關聯在新選單上仍用來解析預設 verb、開啟、開啟方式,並顯示在上方;要把任意自訂命令放到新選單第一層,需要 IExplorerCommand 與識別
assoc["關聯(ProgID + verb)"] --> sol["解析預設/開啟"]
sol --> top["新選單上方"]
assoc -.-> keep["Win11 不需額外工作"]
cmd["自訂命令"] --> need["IExplorerCommand+識別"]
need --> first["新選單第一層"]
圖 13: 關聯在新選單上仍負責「開啟」這一類的解析,只有自訂命令需要 IExplorerCommand + 識別。
6. 實務判斷表 ── 三個選項要選哪一個
先確認 (a) 是否就夠;如果需要把自訂命令顯示在新選單上,就選 (b)。 暫時維持既有的傳統擴充功能則是 (c)。
| 想實現的事 | 建議的做法 | 在 Windows 11 上的呈現 | 需要的工作與成本 |
|---|---|---|---|
| (a) 想用按兩下或「開啟」啟動自家應用程式 | 關聯 + 靜態 verb(只做登錄檔註冊) | 整合顯示在新選單的「開啟」與「開啟方式」 | 只需安裝程式做登錄檔註冊。不需要 DLL,也沒有額外的簽章要求 |
| (b) 想把針對所選檔案/資料夾的自訂命令放上新選單 | 實作 IExplorerCommand + MSIX 資訊清單註冊。未封裝的應用程式用 sparse package 賦予識別 | 新選單的第一層(多個命令會收進以應用程式名稱命名的飛出視窗) | 原生 C++ DLL + 套件識別 + 程式碼簽章 |
| (c) 繼續使用既有的傳統 IContextMenu 擴充功能 | 暫時原樣維持(新開發不要選它) | 只出現在「顯示更多選項」(Shift+F10)的舊選單那一側 | 維持 64 位元建置與 COM 註冊。未來要規劃移轉到 (b) |
判斷 1:選擇能滿足需求的最單純做法
第一原則是:(a) 就能解決的需求,不要搬出 (b) 或 (c)。 殼層擴充功能從寫下它的那一刻起,就要為檔案總管的穩定性負責。
判斷 2:把維持舊選單與改善易用性分開考慮
(c) 只是「沒有壞掉」而已,就使用者體驗而言,它會一直被放在較差的位置。日常操作中使用頻率越高的命令,移轉到 (b) 的投資報酬就越大。
flowchart TB
accTitle: 三個選項怎麼選
accDescr: 若只想按兩下或開啟就啟動,關聯與靜態 verb 就夠;要把自訂命令放到新選單就用 IExplorerCommand 與 MSIX 清單註冊;無法改成 MSIX 就用 sparse package 賦予識別;既有傳統 IContextMenu 擴充功能暫時留在舊選單那一側
q1{"開啟就夠了?"} -->|是| pa["關聯 + 靜態 verb"]
q1 -->|否| q2{"新選單上的自訂命令?"}
q2 -->|是| q3{"能改成 MSIX?"}
q3 -->|是| pb1["IExplorerCommand+MSIX"]
q3 -->|否| pb2["sparse-pkg 識別"]
q2 -->|否| pc["暫時維持傳統"]
pc -.-> old["僅舊選單那一側"]
pa -.-> dllfree["沒有 DLL,風險小"]
圖 14: 依需求從靜態 verb、IExplorerCommand + 識別、維持傳統這三者中選一個。
7. 部署與註冊的實務 ── 安裝程式、sparse package 與清理
實作方式定下來之後,就把註冊位置、變更通知、套件的註冊與解除,以及解除安裝時的善後放進安裝程式。
7.1. 要寫 HKLM 還是 HKCU
註冊位置要和安裝程式的型態一致。面向全體使用者(放到 Program Files、需要系統管理員權限)就用 HKLM\Software\Classes;以使用者為單位安裝(不提升權限)就用 HKCU\Software\Classes。混著用就會產生「A 打得開、B 打不開」這類詢問。
對於伴隨 CLSID 註冊的殼層擴充功能,雖然讓登錄檔註冊本身變得不必要的 Reg-Free COM,在應用程式內部使用 COM 時是有效的選項,但它無法套用到檔案總管要載入的殼層擴充功能,所以還是得走正規的註冊(「什麼是 Reg-Free COM - 免註冊使用 COM 的機制,以及合用與不合用的情境」)。
7.2. 改完要發出通知 ── SHChangeNotify
註冊、變更或刪除關聯之後,要用 SHChangeNotify 送出 SHCNE_ASSOCCHANGED 事件通知。省略它的話,變更有可能直到重新啟動為止都不會被檔案總管認得。110
// 在安裝程式的自訂動作等處,於變更關聯之後呼叫一次
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
7.3. sparse package 的註冊與解除
sparse package 的註冊與解除是安裝程式的工作。註冊在放好檔案之後進行,解除則在刪除檔案之前進行。7
# 安裝時:放好檔案後,把安裝目的地註冊為外部位置
Add-AppxPackage -Path "C:\Program Files\KomuraSoft\KomuraReport.identity.msix" `
-ExternalLocation "C:\Program Files\KomuraSoft"
# 解除安裝時:在刪除檔案之前先解除套件註冊
Remove-AppxPackage <套件的完整名稱>
區分「註冊給執行的使用者」與「部署給全體使用者」
Add-AppxPackage 是針對執行它的使用者註冊的。若從 per-machine 的 MSI 自訂動作以 LocalSystem 執行,實際安裝的那位使用者並不會取得識別。因此要做成以使用者模擬(impersonate)執行的組態。
不過,就算用模擬註冊,拿到註冊的也只有執行那次安裝的使用者。 一台電腦由多位使用者共用時,其他使用者以及之後才建立的使用者並沒有套件識別,新選單上就不會出現命令。
若要讓全體使用者都能用,就要準備以使用者為單位的註冊:例如在應用程式首次啟動時檢查自己的套件註冊,未註冊就註冊。解除安裝時,也要把「從每一位已註冊的使用者解除」納入計畫。
註冊之後選單上沒有生效時
資訊清單註冊要生效,有時需要重新啟動檔案總管(或登出)。6
flowchart TB
accTitle: 註冊與移除 sparse package 的順序
accDescr: 安裝時在放好檔案後註冊 sparse package;解除安裝時在刪檔案之前先移除註冊;注意註冊只對執行它的使用者有效
i1["安裝"] --> i2["放置檔案"]
i2 --> i3["註冊 sparse package"]
u1["解除安裝"] --> u2["移除包註冊"]
u2 --> u3["刪除檔案"]
i3 -.-> pu["註冊只對執行中的使用者有效"]
圖 15: 註冊在放好檔案之後、解除在刪除檔案之前,並注意註冊是以使用者為單位。
7.4. 解除安裝時的清理 ── 該刪什麼、該留什麼
關於解除安裝時的清理,官方指南有明確的方針。1
- 該刪的:自家的整組 ProgID 機碼、Capabilities/RegisteredApplications 註冊、殼層擴充功能的 CLSID 註冊,以及 sparse package(Remove-AppxPackage)。
- 該留的:副檔名機碼(
.kmrpt)的預設值。即使它仍指著自家的 ProgID,官方建議也是不要刪掉。 因為安裝之後很難判斷是否已有別的應用程式接手了預設值,而且 Windows 遇到預設值所指的 ProgID 未註冊時只會單純忽略,留著並不會造成實質損害。 - 清理的最後也要呼叫 SHChangeNotify(SHCNE_ASSOCCHANGED)。
「明明解除安裝了,選單上還是留著殘骸」這類問題,多半是這套清理設計有疏漏。
flowchart TB
accTitle: 解除安裝時的清理設計
accDescr: 解除安裝時刪除內部 ProgID 鍵、CLSID 註冊與 sparse package;留下副檔名鍵預設值,因為未註冊的 ProgID 會被忽略;清理結束時用 SHChangeNotify 通知更改
un["解除安裝"] --> del["刪除"]
un --> keep["留下"]
del --> d1["ProgID 與 CLSID 註冊"]
del --> d2["sparse package"]
keep --> k1["副檔名鍵預設值"]
k1 -.-> why["未註冊的 ProgID 會被忽略"]
d1 --> fin["結束時用 SHChangeNotify 通知"]
k1 --> fin
圖 16: 刪掉自家的註冊、留下副檔名機碼的預設值,並在清理的最後送出變更通知。
8. 疑難排解 ── 不出現、出現兩次、變慢
問題分成「不出現」「出現兩次或消不掉」「變慢或當掉」三類來查。最後說明如何在乾淨的環境中確認註冊與善後。
8.1. 選單上不出現
首先要確認你看的是新選單還是舊選單。 在這之上,再依下面的順序縮小範圍。
- 你看的是哪一邊的選單:舊做法的註冊只會出現在 Shift+F10 的舊選單那一側。先兩邊都確認。
- 位元數:只有 32 位元的殼層擴充功能 DLL 不會被 64 位元的檔案總管載入(4.3 節)。
- 註冊位置:HKLM/HKCU 或 Wow6432Node 弄錯了。用
reg query確認實際的機碼。 - 套件註冊:若是針對新選單,就用
Get-AppxPackage確認有沒有註冊、簽章憑證是否受信任,以及-ExternalLocation的路徑,然後重新啟動檔案總管。6 - 漏發通知:如果是忘了 SHChangeNotify,可以用「重新啟動檔案總管後會不會生效」來判別。
flowchart TB
accTitle: 選單上不出現時的隔離順序
accDescr: 先確認你看的是哪一面選單,再依序隔離 DLL 位元數、登錄檔註冊目標、包註冊與簽章,以及漏掉的 SHChangeNotify
c1["確認看的是舊或新選單"] --> c2["確認 DLL 位元數"]
c2 --> c3["確認 HKLM 與 HKCU 註冊目標"]
c3 --> c4["確認包註冊與簽章"]
c4 --> c5["用重啟判斷是否漏通知"]
圖 17: 「不出現」時,依看的是哪邊選單、位元數、註冊位置、套件註冊、漏發通知的順序釐清。
8.2. 出現兩次或消不掉
先從重複出現在哪一邊的選單來推測方向。
- 只在舊選單出現兩次: 懷疑是解除安裝時漏了清理(7.4 節),或是舊版 ProgID 的殘骸。
- 新舊兩邊都出現: 懷疑是舊做法的登錄檔註冊與 MSIX 資訊清單註冊同時存在。
這兩者都只是典型原因的初步推測,最後仍要確認實際的註冊內容再下判斷。
flowchart TB
accTitle: 隔離雙重顯示
accDescr: 只在舊選單出現兩次指向清理漏洞或舊 ProgID 等殘留;新舊兩邊都出現兩次指向傳統登錄檔註冊與清單註冊並存
q{"在哪裡出現兩次?"} -->|僅舊選單| zan["殘留"]
q -->|新舊兩邊| hei["並存"]
zan -.-> z1["清理漏洞或留下的舊 ProgID"]
hei -.-> h1["傳統登錄檔註冊與新註冊並存"]
圖 18: 只在舊選單重複就偏向殘骸類,新舊兩邊都出現就偏向並存類。
8.3. 檔案總管變慢或當掉
按右鍵反應慢、或在特定資料夾當掉時,先盤點已安裝的殼層擴充功能。
- 列出擴充功能。 用 NirSoft 的 ShellExView 之類的工具,確認非 Microsoft 出品的擴充功能。
- 暫時停用以縮小範圍。 對可疑的項目用二分法逐步排除,找出問題來源的 DLL。若是當掉,事件檢視器裡的「發生錯誤的模組」也是線索。
- 若是自家的擴充功能,就檢查建構選單的路徑。 懷疑同步 I/O 或網路存取(4.2 節、5.2 節)。
flowchart TB
accTitle: 變重或崩潰時找出元兇 DLL
accDescr: 在 ShellExView 列出非 Microsoft 殼層擴充功能,暫時禁用可疑項並用二分法找出元兇 DLL;崩潰時事件檢視器的 faulting module 也是線索
s1["盤點殼層擴充功能"] --> s2["列出非 Microsoft 的"]
s2 --> s3["暫時禁用並二分法"]
s3 --> s4["找出元兇 DLL"]
crash["崩潰時"] -.-> ev["檢查 faulting module"]
ev -.-> s4
圖 19: 一邊暫時停用非 Microsoft 擴充功能一邊用二分法排除,當掉時併用事件檢視器。
8.4. 驗證時 Windows Sandbox 很方便
驗證殼層整合,基本上就是確認「在乾淨的環境安裝→執行→解除安裝→零殘骸」。這時好用的是 Windows Sandbox(Pro/Enterprise/Education),每次啟動都會在數秒內開出一個全新的拋棄式 Windows,因此安裝程式的註冊與清理測試可以反覆跑很多次。關掉之後一切都會消失,也很適合用來調查登錄檔的殘骸。15
9. 總結
如果換成 Windows 11 之後被反映「選單被藏起來了」,請先用第 6 章的判斷表確認你想實現的到底是什麼。思考的順序分成下面三個階段。
1. 判斷關聯是否就夠
若只是「用這個應用程式開啟」,現在靠關聯與靜態 verb 依然足夠。底層是副檔名機碼→ProgID→verb,而 HKCR 是把 HKLM/HKCU 的 Classes 疊起來、供確認用的檢視。寫入位置要寫明,%1 一定要用引號括起來。
不過,選擇預設應用程式的是使用者。安裝程式不該搶走預設值,而要把自己正確註冊成候選。
2. 決定把自訂命令放到哪一邊的選單
傳統的 IContextMenu 擴充功能,是在以「顯示更多選項」開啟的舊選單那一側運作。要把自訂命令放到新選單,就需要 IExplorerCommand 以及 MSIX 資訊清單註冊。無法全面改成 MSIX 的應用程式,用 sparse package 取得套件識別是務實的解法。
傳統擴充功能的 DLL 是在檔案總管等處理程序內執行的。當掉或延遲都會波及整個宿主,64 位元的宿主需要 64 位元 DLL。以受控程式碼撰寫處理程序內擴充功能並不受支援,原則上要用原生 C++ 實作。
3. 把註冊、變更通知與解除當成一組來驗證
註冊、變更或刪除關聯之後要用 SHChangeNotify 發出通知。解除安裝時刪掉自家的 ProgID 等項目,保留副檔名機碼的預設值。sparse package 是以使用者為單位註冊的,所以在多使用者環境中,也要把每位使用者的註冊與解除納入計畫。
用 Windows Sandbox 反覆執行在乾淨環境安裝→執行→解除安裝→確認殘骸這一輪。
從最單純的手段選起,只有在必要時才進到把自訂命令加入新選單。照這個順序走,就能理清因應的規模,以及該維護的註冊與實作範圍。
相關文章
- COM / ActiveX / OCX 是什麼 - 差異與關係一次整理
- 什麼是 Reg-Free COM - 免註冊使用 COM 的機制,以及合用與不合用的情境
- 登錄檔的 32bit/64bit 重新導向與虛擬化的陷阱 ── Wow6432Node 與「明明寫入了卻讀不到值」的問題
- Windows 應用發布方式怎麼選 - MSI / MSIX / ClickOnce / xcopy / 自訂 updater 的判斷表
- DLL・COM 介面的向後相容性 ── 哪些變更會破壞呼叫端的判斷表
- Windows 應用程式相容如何運作 ── 用相容模式、Shim 與 Compatibility Administrator 延續舊應用
相關諮詢領域
小村軟體有限公司承接業務應用程式的檔案關聯、右鍵選單與殼層擴充功能的設計與實作,Windows 11 新內容功能表的因應(改用 IExplorerCommand、導入 sparse package),既有安裝程式註冊與清理的檢討,以及檔案總管變慢或當掉的原因調查。從「被藏進顯示更多選項的選單該怎麼辦」這種方針討論開始也可以。
參考連結
-
Microsoft Learn, File Types. 關於副檔名機碼指向 ProgID 的結構、OpenWithProgIds、寫到 HKLM/HKCU\Software\Classes 的區分方式、變更關聯後應呼叫 SHChangeNotify(SHCNE_ASSOCCHANGED),以及解除安裝時應刪除 ProgID 但保留副檔名機碼的預設值。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Choosing a Static or Dynamic Shortcut Menu Method. 關於應選擇能滿足需求的最單純靜態 verb 做法、IContextMenu 最強大但也最複雜並被歸在不建議的那一側,以及 IExplorerCommand/IExplorerCommandState 才是建議的做法。 ↩ ↩2
-
Microsoft Learn, HKEY_CLASSES_ROOT Key. 關於 HKEY_CLASSES_ROOT 是 HKLM\Software\Classes 與 HKCU\Software\Classes 的合成檢視、使用者端的定義優先於電腦端,以及寫入時的分派規則。 ↩ ↩2
-
Microsoft Learn, Windows app defaults platform. 關於變更預設應用程式被設計成只能透過系統設定 UI 進行、使用者設定資料經過混淆並由篩選器驅動程式(UCPD.sys)防寫、以登錄檔為基礎的變更不受支援,以及在受管理環境中使用群組原則/MDM 原則。 ↩ ↩2
-
Windows Developer Blog, Extending the Context Menu and Share Dialog in Windows 11. 關於 Windows 11 新內容功能表的設計、以 IExplorerCommand 加上應用程式識別進行擴充、把「開啟」與「開啟方式」放在上方、把多個命令收進帶有應用程式名稱的飛出視窗,以及傳統 IContextMenu 擴充功能會以「顯示更多選項」(Shift+F10)的 Windows 10 選單形式載入。 ↩ ↩2 ↩3
-
Microsoft Learn, Add a File Explorer context menu command to a packaged desktop app. 關於註冊到 Windows 11 新內容功能表是以實作 IExplorerCommand 加上 windows.comServer 與 desktop4:FileExplorerContextMenus 的資訊清單宣告來完成、ItemType 可指定 *、Directory 與 Directory\Background、DLL 的架構要一致、建構選單的方法要保持快速、以 sparse package 因應未封裝的應用程式、註冊要生效有時需要重新啟動檔案總管,以及檔案關聯並不是通用的選單擴充機制。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Grant package identity by packaging with external location. 關於不更動既有安裝程式,就能以註冊帶外部位置的套件(sparse package)取得套件識別、自 Windows 10 版本 2004 起可用,以及讓必須具備識別的 Windows 功能(內容功能表註冊、通知等)變得可用。 ↩ ↩2 ↩3
-
Microsoft Learn, Working with Shell Extensions. 關於殼層擴充處理常式的種類、擴充功能是被載入檔案總管(以及承載殼層的處理程序)的處理程序內 COM DLL,因此當掉或卡住會波及整個檔案總管、以 ThreadingModel=Apartment 註冊,以及在撰寫殼層擴充功能之前應先考慮更單純的替代做法。 ↩ ↩2 ↩3
-
Microsoft Learn, Guidance for Implementing In-Process Extensions. 關於 Microsoft 不建議也不支援用受控程式碼實作處理程序內的殼層擴充功能、理由包括 CLR 版本衝突、重入與物件存留期的非決定性,以及處理程序外擴充功能(預覽處理常式,或從 shell\verb\command 啟動)可以使用受控程式碼。 ↩ ↩2 ↩3
-
Microsoft Learn, SHChangeNotify function. 關於如何發出通知系統「檔案關聯已變更」的 SHCNE_ASSOCCHANGED 事件,以及用它讓殼層認得變更的方式。 ↩ ↩2
-
Microsoft Learn, Application Registration. 關於建議以 App Paths 子機碼註冊執行檔、Applications 子機碼的角色、以 SystemFileAssociations 註冊 verb,以及變更預設應用程式時 ProgID 與相關資訊的優先順序。 ↩
-
Microsoft Learn, Verbs and File Associations. 關於 verb 也是 ShellExecuteEx 所使用的動詞、命令字串中可能含空格的元素必須用引號括住且 “%1” 一律要加引號,以及在 HKCR\Applications 底下註冊預設處理程序。 ↩
-
Microsoft Learn, IExplorerCommand interface. 關於 GetTitle、GetIcon、GetState、Invoke、EnumSubCommands 等方法的組成、方法是從 UI 執行緒呼叫因此不得與網路資源通訊,以及自 Windows Vista 起可用。 ↩
-
Microsoft Learn, Windows Sandbox. 關於能在數秒內啟動拋棄式、隔離的 Windows 環境、關閉後會捨棄所有變更、適合軟體測試與安裝程式驗證,以及在 Pro/Enterprise/Education 版可用。 ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Windows 列印驅動程式停止提供 ── 業務應用程式的報表與標籤列印如何因應
Microsoft 正分階段推動 v3/v4 列印驅動程式的停止提供,從 2026 年 7 月起會優先選用 IPP 類別驅動程式。本文整理 Windows protected print mode 之下會消失哪些東西,並以判斷表梳理業務應用程式的報表、標籤列印中相依處的盤點...
WinRT 就是 COM —— IInspectable、.winmd、語言投影,以及 WinUI 至今仍立在二進位契約之上的原因
WinRT 不是受管理的執行階段,而是在 COM 之上加了中繼資料(.winmd)與語言投影的 ABI。本文從 IUnknown 與 IInspectable 的關係,一路談到桌面應用程式裡 HWND 初始化與 package identity 的卡關之處。
什麼是 OLE 物件 —— 內嵌與連結的機制以及業務文件中的陷阱
在 Word 中內嵌 Excel 表格的功能,本質就是 OLE 物件。本文從內嵌與連結的差異、複合檔案與結構化儲存體、In-Place Activation 的機制,一路談到連結中斷、檔案膨脹與資安對策,全部立足於實務視角。
剪貼簿與拖放的運作方式 ── 在業務應用程式中正確處理 OLE 資料傳輸
貼上 Excel 表格就散掉、關掉複製來源就貼不上──真正的原因,是剪貼簿把同一份內容以多種格式同時放上。本文從標準格式、延遲轉譯、OLE 拖放,一路談到剪貼簿歷程與雲端同步的原則。
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
Windows 軟體維護 & 現代化
支援既有 Windows 軟體的階段性升級、功能追加、64 位元就緒以及可維護性的重構。
常見問題
整理諮詢這個主題時常見的問題。
- 為什麼在 Windows 11 上,自家應用程式的右鍵選單項目只出現在「顯示更多選項」裡面?
- 因為 Windows 11 把檔案總管的右鍵選單拆成了新舊兩層。能把項目放上新選單的,只有實作 IExplorerCommand 介面、並在 MSIX 套件的資訊清單中註冊(也就是具備套件識別)的命令;傳統以 IContextMenu 為基礎的殼層擴充功能,則被移到以「顯示更多選項」(Shift+F10)開啟的舊選單那一側。擴充功能本身並沒有壞掉,所以目前仍能照常運作;但若希望它出現在新選單上,就需要改用 IExplorerCommand,並藉由改成 MSIX 或 sparse package 取得套件識別。
- 可以由安裝程式把自家應用程式設定成檔案的預設應用程式(按兩下就開啟的那個)嗎?
- 不行。預設應用程式的選擇被設計成由使用者決定,Windows 並不支援從系統設定 UI 以外的地方變更預設應用程式。保存每位使用者選擇的 UserChoice 資訊經過混淆處理,而且還有篩選器驅動程式(UCPD.sys)阻擋應用程式寫入。安裝程式能做的,只到註冊 ProgID 與 verb、把自己加進 OpenWithProgIds 以出現在「開啟方式」的候選清單,以及把使用者引導到預設應用程式的設定畫面為止。正確的做法不是搶走預設值,而是把被選中的條件準備好。
- 可以用 C# 之類的受控程式碼撰寫殼層擴充功能嗎?
- Microsoft 已明確表示,用受控程式碼撰寫會被載入到處理程序內的殼層擴充功能(內容功能表處理常式、圖示處理常式等),既不建議也不在支援範圍內。因為擴充功能會被載入到檔案總管,以及任何開啟通用檔案對話方塊的應用程式處理程序中,CLR 的版本衝突、重入,以及物件存留期的非決定性,都會讓宿主應用程式變得不穩定。實作上原則是使用原生 C++。另一方面,若是由 verb 的 command 啟動的一般 EXE,或在另一個處理程序中執行的處理程序外擴充功能(例如預覽處理常式),用受控程式碼就沒有問題。
- 什麼是 sparse package(帶外部位置的 MSIX)?
- 它是一個不含應用程式本體檔案、只帶資訊清單(識別資訊)的小型 MSIX 套件。對於以現有安裝程式(MSI、Inno Setup 等)正常安裝的應用程式,只要用 Add-AppxPackage 的 -ExternalLocation 指向安裝資料夾來註冊,該應用程式就能取得套件識別,進而使用註冊到 Windows 11 新內容功能表、快顯通知等必須具備識別的功能。它從 Windows 10 版本 2004 起可用,而且套件需要以目標電腦所信任的憑證做程式碼簽章。當你不想把發布方式全面改成 MSIX、又想支援新選單時,這是務實的選項。
- 右鍵選單的項目出現兩次,或是怎麼都消不掉時該怎麼辦?
- 先釐清原因:確認它出現在新選單還是舊選單(顯示更多選項)。重複顯示的典型原因,是舊做法的登錄檔註冊與 MSIX 資訊清單註冊同時存在,或是解除安裝時沒有清掉 ProgID 與擴充功能的 CLSID 註冊而留下殘骸。變更關聯之後,還要懷疑是否漏了 SHChangeNotify(SHCNE_ASSOCCHANGED) 的通知;剛註冊完套件時,則要懷疑是否漏了重新啟動檔案總管。若還是沒解決,可以用 ShellExView 暫時停用非 Microsoft 出品的擴充功能,再以二分法逐步縮小範圍,就能找出問題來源的 DLL。