登錄檔的 32bit/64bit 重新導向與虛擬化的陷阱 ── Wow6432Node 與「明明寫入了卻讀不到值」的問題

· · 登錄檔, WOW64, Wow6432Node, UAC, 64bit 移轉, Windows, C#, 故障排除

「安裝程式寫入的授權金鑰,應用程式啟動時讀不到。用 regedit 看確實有這個值」──在協助某套裝置連動軟體進行 64bit 移轉時,收到過這樣的回報。查下去才發現,值其實存在於 HKLM\Software\Wow6432Node\公司名稱 底下。而重新以 64bit 建置的應用程式,讀取的是 HKLM\Software\公司名稱,那裡當然什麼都沒有——就是這麼一個結局。

這類「明明寫入了卻沒有值」「regedit 看得到,應用程式卻看不到」的問題,如果不了解兩個機制,再怎麼肉眼檢查也只會一頭霧水。第一個是,64bit Windows 上的登錄檔會依處理序的 bitness(該處理序是以 32bit 還是 64bit 運作的區別)而顯示在不同位置 ── 也就是 WOW64(Windows 32-bit on Windows 64-bit,64bit Windows 用來執行 32bit 應用程式的子系統)所做的登錄檔重新導向。另一個是,權限不足的寫入操作會被靜默轉送到別處 ── 也就是 UAC 的登錄檔虛擬化。而且這兩者都是「不會出錯、就這樣成功了」,所以問題會在寫入位置與讀取位置不一致的那一刻才浮現,也就是 64bit 移轉或更換安裝程式的時機。

本文以 Windows 業務應用程式開發者為對象,整理 WOW64 登錄檔重新導向的機制與受重新導向影響的機碼、共用機碼的分類、UAC 登錄檔虛擬化的觸發條件、安裝程式與 COM 註冊中會實際發生的損害,以及在 C#/C++/reg.exe 中明確指定視圖的正確寫法,並附上官方文件佐證。

1. 先講結論

  • 64bit Windows 的登錄檔有 64bit 視圖與 32bit 視圖之分,32bit 處理序對 HKLM\Software 的存取,會被登錄檔重新導向器透明地導向實體位置 HKLM\Software\Wow6432Node應用程式端看不到任何錯誤或警告。1
  • Wow6432Node 這個實體位置是系統保留區域。不可以把路徑寫死後直接存取(在 Windows 10 on ARM 上,32bit ARM 用的是另一個位置 WowAA32Node)。要存取其他視圖,請使用官方提供的手段(後述的旗標或 RegistryView)。12
  • 只有一部分機碼會被重新導向,也有機碼是共用的。在 Windows 7 以後,HKLM\SOFTWARE 是「重新導向」,HKLM\SOFTWARE\Classes 是「共用」,但其下的 CLSIDInterface 又是「重新導向」,呈現一種巢狀結構。3
  • 要存取其他視圖,在 Win32 中是在 RegOpenKeyEx 等函式的 samDesired 指定 KEY_WOW64_64KEY / KEY_WOW64_32KEY,在 .NET 中是在 RegistryKey.OpenBaseKey 指定 RegistryView.Registry64 / Registry32,在命令列則是 reg.exe/reg:64 / /reg:32245
  • 沒有系統管理員權限的 32bit 互動式處理序寫入 HKLM\Software 時,可能會因 UAC 登錄檔虛擬化而被轉送到 HKEY_USERS\<SID>_Classes\VirtualStore\Machine\Software寫入操作會「成功」,而且該使用者自己讀回時看到的是合併視圖,這正是陷阱所在。6
  • 在資訊清單中指定 requestedExecutionLevel 後,檔案/登錄檔虛擬化就會停用。反過來說,只有沒有資訊清單的舊版 EXE 才會受虛擬化影響。7
  • 登錄檔虛擬化是過渡性的相容技術,Microsoft 明確表示打算在未來的 Windows 版本中移除。新開發的應用程式不應依賴它。正確的設計是「不寫入 HKLM」。6
  • COM 的 CLSID 註冊(HKCR\CLSID = HKLM\Software\Classes\CLSID)會依 bitness 分開。32bit COM 伺服器的註冊,從 64bit 用戶端是看不到的,這是「找不到類別(0x80040154)」的典型成因。3

兩個機制的全貌

本文討論的這兩個機制,由誰、把什麼、轉送到哪裡是完全不同的事。重新導向是「bitness 的問題」,對讀寫都有影響;虛擬化是「權限的問題」,只影響寫入。這兩者很容易混淆,在深入細節之前,先用一張圖掌握整體概念。

32bit64bit無資訊清單的 32bit 互動式處理序64bit 處理序-服務-有資訊清單處理序操作 HKLM 下的 Software處理序是否為 32bitWOW64 登錄檔重新導向bitness 的問題-對讀寫都有影響不重新導向直接讀寫原本的 HKLM Software實際讀寫的是Wow6432Node 之下 - 32bit 視圖寫入時-對該機碼沒有寫入權限UAC 登錄檔虛擬化權限的問題-只影響寫入轉送到使用者專屬的 VirtualStore讀取時為與原位置的合併視圖以存取拒絕直接失敗

這張圖左側的分支(是否為 32bit)對應第 2、3 章,右下方的分支(權限與資訊清單)對應第 5 章的內容。

2. WOW64 的登錄檔重新導向 ── 32bit 處理序看到的是哪裡

64bit Windows 會在 WOW64 這個子系統上執行 32bit 應用程式。此時登錄檔重新導向器會分別讓 32bit 處理序與 64bit 處理序看到各自不同的邏輯視圖。兩者用同一組 API、指定同一個機碼名稱(HKEY_LOCAL_MACHINE\Software\...),但實際讀寫的實體位置卻不同——這正是這個機制的核心。1

被重新導向的機碼,其實體位置就是 Wow6432Node。舉例來說,32bit 處理序存取的 HKEY_LOCAL_MACHINE\Software,實體上會對應到 HKEY_LOCAL_MACHINE\Software\Wow6432Node。這個對應對應用程式而言是完全透明的,32bit 應用程式可以「當作自己在 32bit Windows 上運作」一樣操作登錄檔。1

把「誰看到哪裡」整理成表格如下。

存取來源 程式碼中指定的路徑 實際讀寫的實體位置
64bit 處理序 HKLM\Software\MyApp HKLM\Software\MyApp
32bit 處理序 HKLM\Software\MyApp HKLM\Software\Wow6432Node\MyApp
32bit 處理序 + KEY_WOW64_64KEY(RegistryView.Registry64) HKLM\Software\MyApp HKLM\Software\MyApp
64bit 處理序 + KEY_WOW64_32KEY(RegistryView.Registry32) HKLM\Software\MyApp HKLM\Software\Wow6432Node\MyApp
32bit 互動式處理序・標準權限・無資訊清單的寫入 HKLM\Software\MyApp HKCU\Software\Classes\VirtualStore\Machine\Software\Wow6432Node\MyApp(先經 WOW64 重新導向,再虛擬化。參見第 5 章)
regedit(64bit 處理序) 以 64bit 視圖為基準顯示,Wow6432Node 也會以實體機碼原樣顯示出來

文章開頭的失敗案例,正是這張表的具體呈現。32bit 安裝程式寫入的值,實體上位於 Wow6432Node 之下,而改成 64bit 的應用程式讀取的卻是原本的 HKLM\Software。regedit 兩邊都看得到,因此「regedit 上有值」的現場回報,與「應用程式讀不到」的現象,兩者並不矛盾,可以同時成立。

順帶一提,這時候可能會想把 Wow6432Node 的路徑直接寫死在程式中以求對齊,但這正是官方文件明確禁止的反模式。重新導向所指向的實體位置屬於系統保留區,可能會被變更。實際上,在 Windows 10 on ARM 上,32bit ARM 應用程式的重新導向目標就是另一個機碼 WowAA32Node12 若想讀取其他視圖,請使用第 4 章介紹的官方手段。

再補充一個細節:WOW64 還會做一項修正——把 32bit 應用程式寫入的、以 %ProgramFiles% 開頭的 REG_SZ/REG_EXPAND_SZ 字串,置換成 %ProgramFiles(x86)%(僅在大小寫也完全相符時才會置換)。1 此外,在 Vista/XP 時代,曾經有一種在 32bit/64bit 視圖之間複製機碼以同步的機制,稱為「登錄檔反射(Registry Reflection)」,但已在 Windows 7/Windows Server 2008 R2 中廢除。閱讀較舊的解說文章時,請留意這個前提上的差異。1

3. 會被重新導向的機碼與共用的機碼

並非所有機碼都會被重新導向。部分機碼在兩個視圖中共用同一份實體副本。以下從官方文件「Registry Keys Affected by WOW64」的清單中,摘錄與業務應用程式開發關係較密切的部分(Windows 7/Server 2008 R2 以後的欄位。子機碼原則上會繼承父機碼的行為)。3

機碼 Windows 7 以後的處理方式
HKLM\SOFTWARE 重新導向
HKLM\SOFTWARE\Classes 共用
HKLM\SOFTWARE\Classes\CLSID 重新導向
HKLM\SOFTWARE\Classes\Interface 重新導向
HKLM\SOFTWARE\Classes\DirectShow / Media Type / MediaFoundation 重新導向
HKLM\SOFTWARE\Clients 共用
HKLM\SOFTWARE\Microsoft\COM3 / EventSystem / OLE / RPC 共用
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths 共用
HKLM\SOFTWARE\Policies 共用
HKCU\SOFTWARE 共用
HKCU\SOFTWARE\Classes 共用
HKCU\SOFTWARE\Classes\CLSID / Interface 重新導向

值得注意的是這個巢狀結構。HKLM\SOFTWARE 會被重新導向,但其下的 Classes 卻又回到共用,而再往下的 CLSIDInterface 又再次被重新導向。若抱持「Software 之下全部都會跑到 Wow6432Node」這種粗略理解,就無法解釋副檔名關聯(Classes 直屬,共用)與 COM 類別註冊(Classes\CLSID,重新導向)在行為上的差異。HKCU 基本上是共用的,因此只要把使用者專屬的設定放在 HKCU,幾乎不會遇到 bitness 問題——這在實務上是相當重要的結論。3

另外,KEY_WOW64_64KEY 這類旗標對共用機碼沒有作用。共用機碼本來就只有一份實體,切換視圖並沒有意義。2

4. 明確讀取其他視圖 ── reg.exe・C#・C++ 的正確寫法

要確認「值到底在哪個視圖」,最快的方法是使用 reg.exe 的 /reg:64 / /reg:32 選項。它們分別明確指定存取 64bit 視圖與 32bit 視圖。5

:: 讀取 64bit 視圖(原本的 HKLM\Software)
reg query "HKLM\SOFTWARE\KomuraSoft\DeviceLink" /v LicenseKey /reg:64

:: 讀取 32bit 視圖(實體上位於 Wow6432Node 之下)
reg query "HKLM\SOFTWARE\KomuraSoft\DeviceLink" /v LicenseKey /reg:32

執行這兩行指令,若只有其中一邊有值,就可以確定「寫入端與讀取端的 bitness 不一致」。重點在於不要把 Wow6432Node 手動寫進路徑,而是針對同一個邏輯路徑,只切換視圖。

在 C#(.NET)中,是在 RegistryKey.OpenBaseKey 傳入 RegistryView。有 Registry64(值 256)、Registry32(值 512)、Default(值 0)三種,可以透過 OpenBaseKey / OpenRemoteBaseKey / FromHandle 指定。4

using Microsoft.Win32;

// 即使從 32bit 處理序也讀取 64bit 視圖
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
using (var key = baseKey.OpenSubKey(@"SOFTWARE\KomuraSoft\DeviceLink"))
{
    var license = key?.GetValue("LicenseKey") as string;
}

// 從 64bit 處理序讀取 32bit 視圖(Wow6432Node 側)
// ── 可用於讀取 32bit 時代安裝程式所寫入的值,做遷移用途
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
using (var key = baseKey.OpenSubKey(@"SOFTWARE\KomuraSoft\DeviceLink"))
{
    var legacy = key?.GetValue("LicenseKey") as string;
}

RegistryView.Default 會交由處理序自身的 bitness 決定。AnyCPU 建置的 .NET 應用程式,32bit/64bit 會依執行環境而異,因此若要處理 HKLM 之下、機器層級共用的資料,在程式碼中明確指定要讀取哪個視圖,可以避免因建置設定變更(切換 Prefer 32-bit,或進行 64bit 移轉)而使登錄檔的可見內容突然改變的事故。另外,在 32bit OS 上要求 Registry64 時,規格上會回傳 32bit 視圖的機碼,因此即使仍需支援 32bit OS,同一份程式碼也能安全運作。4

在 C++(Win32 API)中,是在 RegOpenKeyEx / RegCreateKeyEx / RegDeleteKeyExsamDesired 中以 OR 運算指定 KEY_WOW64_64KEY(0x0100)或 KEY_WOW64_32KEY(0x0200)。若同時指定兩者,會以 ERROR_INVALID_PARAMETER 失敗。2

HKEY hKey = nullptr;
// 從 32bit 處理序開啟 64bit 視圖的機碼
LSTATUS st = RegOpenKeyExW(
    HKEY_LOCAL_MACHINE,
    L"SOFTWARE\\KomuraSoft\\DeviceLink",
    0,
    KEY_READ | KEY_WOW64_64KEY,   // 在此明確指定視圖
    &hKey);
if (st == ERROR_SUCCESS)
{
    wchar_t buf[256]; DWORD cb = sizeof(buf); DWORD type = 0;
    RegQueryValueExW(hKey, L"LicenseKey", nullptr, &type,
                     reinterpret_cast<LPBYTE>(buf), &cb);
    RegCloseKey(hKey);
}

官方文件提出了兩個要留意的地方。一旦以旗標開啟了其他視圖,對其下子機碼的操作(建立・刪除・開啟)也要持續明確指定相同旗標。若混用不同旗標,會導致非預期的行為。另外,若要不遺漏地列舉兩個視圖中的機碼,需要用 KEY_WOW64_64KEY 開啟的控制代碼,以及用 KEY_WOW64_32KEY 開啟的控制代碼,分兩趟列舉。還要注意 RegDeleteKey(不帶 Ex 的版本)無法存取其他視圖。2

5. UAC 登錄檔虛擬化 ── 明明寫入了 HKLM,值卻在 VirtualStore 裡

容易與重新導向混淆的另一個機制,是 UAC 的登錄檔虛擬化。這不是 bitness 的問題,而是權限的問題,自 Vista 起導入,是為了拯救以系統管理員權限為前提所撰寫的舊版應用程式的相容技術。6

運作方式是這樣的:沒有寫入權限的處理序嘗試在 HKLM\Software 之下寫入值或建立子機碼時,不會以存取拒絕失敗,而是這個寫入操作會被轉送到使用者專屬的虛擬儲存區 HKEY_USERS\<使用者 SID>_Classes\VirtualStore\Machine\Software(在 regedit 中會顯示於 HKCU\Software\Classes\VirtualStore\Machine\Software 之下)。而且讀取時,會回傳虛擬儲存區的值與原本全域儲存區的值的合併視圖,同名的值以虛擬儲存區優先。6 另外,在 64bit OS 上的 32bit 處理序,由於第 3 章介紹的 WOW64 重新導向會先生效,對 HKLM\Software\MyApp 的寫入,實際上會被轉送到 VirtualStore\Machine\Software\Wow6432Node\MyApp。調查 VirtualStore 之下時,除了原本的 Software 側之外,務必也確認 Wow6432Node 側。

換句話說,對寫入的那個處理序自己來說,讀寫都會像什麼事都沒發生一樣正常運作。這正是「開發機(以系統管理員身分執行)沒有問題,只有在客戶端的標準使用者環境才出現設定異常」「A 使用者登入時可以運作,B 使用者登入時卻回到初始值」這類因使用者而異的怪異故障的真正原因。虛擬儲存區是使用者設定檔(NTUSER.DAT 等)的一部分,因此每位使用者的內容都不同(關於設定檔的結構,請參考「Windows 使用者設定檔入門 - AppData 與 NTUSER.DAT」)。

虛擬化生效的條件相當有限,官方文件有明確記載。6

  • 只有32bit 互動式處理序HKLM\Software 之下系統管理員能寫入的機碼進行的操作才會受到影響
  • 以下情況不受影響:64bit 處理序、服務等非互動式處理序、正在模擬使用者身分(impersonation)的操作、驅動程式,以及在資訊清單中指定了 requestedExecutionLevel 的處理序
  • HKLM\Software\ClassesHKLM\Software\Microsoft\WindowsHKLM\Software\Microsoft\Windows NT 之下也不受影響

在實務上特別關鍵的一點是「有無資訊清單會改變行為」。Visual Studio 的 C++ 連結器預設會在資訊清單中內嵌 asInvoker 的 UAC 片段7,因此用現代工具鏈建置出來的 EXE,一開始就不受虛擬化影響。會遇到虛擬化的現場,通常是在 64bit Windows 上執行沒有資訊清單的 VB6/舊版 Delphi/舊版 VC++ 製作的舊版 EXE。反過來說,若對舊版 EXE「先姑且加上資訊清單」或「重新建置成 64bit」,虛擬化就會失效,結果變成真正遭遇存取拒絕而當掉(或靜默寫入失敗)——這也是移轉時常見的陷阱。關於 requestedExecutionLevel 的三個值(asInvoker / highestAvailable / requireAdministrator)以及權限設計的思路,在「Windows 什麼時候需要系統管理員權限」中有詳細說明。

要強調的是,官方文件明確指出虛擬化是過渡性(interim)的相容技術,並打算在未來的 Windows 版本中移除。新開發依賴這個行為完全不可取,設計原則應該是「應用程式不寫入敏感的系統區域(HKLM)。資料應放在使用者專屬的位置,或附加了適當 ACL 的共用位置」。6 關於該把什麼資料存放在哪裡的判斷,在「Windows 應用程式資料儲存位置怎麼選」中有整理。

另外,也有可以依機碼個別控制虛擬化的旗標(REG_KEY_DONT_VIRTUALIZE / REG_KEY_DONT_SILENT_FAIL / REG_KEY_RECURSE_FLAG),可以用 reg.exe 的 flags 選項來查詢與設定。官方文件所列的查詢範例與其輸出如下。6

C:\>reg flags HKLM\Software\AppKey1 QUERY
HKEY_LOCAL_MACHINE\Software\AppKey1
        REG_KEY_DONT_VIRTUALIZE: CLEAR
        REG_KEY_DONT_SILENT_FAIL: CLEAR
        REG_KEY_RECURSE_FLAG: CLEAR
The operation completed successfully.

三個都是 CLEAR,意思是「沒有設定該旗標」= 這個機碼的虛擬化行為維持預設。設定(SET)各旗標後的效果如下。6

旗標 設定後的效果
REG_KEY_DONT_VIRTUALIZE 停用寫入的虛擬化。權限不足時的建立機碼・設定值操作,不會被轉送到 VirtualStore,而是直接失敗
REG_KEY_DONT_SILENT_FAIL 停用開啟的虛擬化。不再以 MAXIMUM_ALLOWED 重新開啟權限不足的開啟操作來救援,而是直接失敗
REG_KEY_RECURSE_FLAG 讓虛擬化旗標從父機碼傳播到子機碼。只對變更後才建立的子機碼生效,不會套用到既有的子機碼

也就是說,如果想「只讓這個機碼停止虛擬化,把權限不足的問題表面化」,可以設定 REG_KEY_DONT_VIRTUALIZE。設定旗標時要把 QUERY 換成 SET,正確的選項排列方式請以 reg flags /? 為準。

在故障調查時,有兩個快速的確認手段:在工作管理員的「詳細資料」分頁中,於欄位標題按右鍵,從「選擇資料行」加入「UAC 虛擬化」,以此查看各處理序的虛擬化狀態;以及檢查 HKCU\Software\Classes\VirtualStore 之下是否留有被轉送的殘留值。

6. 安裝程式與 COM 註冊中會實際發生的損害

這兩個機制最容易在實務上釀成實際損害的地方,就是安裝程式與 COM 註冊。

安裝程式的 bitness 問題。32bit 的安裝程式(32bit MSI 或 32bit 安裝 EXE)寫入 HKLM\Software\公司名稱 的設定,實體上會落在 Wow6432Node 之下。若應用程式本體已改成 64bit,但安裝程式仍沿用 32bit 版本,就會如文章開頭的失敗案例一樣,出現「安裝程式寫入了,應用程式卻讀不到」。反過來的組合(64bit 安裝程式 + 32bit 應用程式)也是同樣道理。對策是讓安裝程式與應用程式本體對齊「寫入哪個視圖、讀取哪個視圖」,並在 64bit 移轉的過渡期,用第 4 章介紹的 RegistryView.Registry32 實作從舊位置讀取的遷移邏輯。

COM 註冊的 bitness 問題。如第 3 章的表格所示,HKLM\Software\Classes\CLSID(= HKCR\CLSID 的 HKLM 側)是重新導向的對象。也就是說32bit COM 伺服器的 CLSID 註冊會落在 32bit 視圖,64bit 的註冊會落在 64bit 視圖,彼此互相看不到。3 64bit 處理序原本就無法載入 32bit DLL 的處理序內 COM 伺服器,所以這種分離本身是合理的,但在現場往往會以「明明用 regsvr32 註冊了,用戶端卻出現 0x80040154(找不到類別)」的形式爆發。regsvr32 本身也分成 32bit 版(SysWOW64 側)與 64bit 版(System32 側),用哪個版本註冊,決定了寫入哪個視圖。COM 註冊與 bitness 組合所引發的各種陷阱的全貌,整理在「開發 COM 元件、OCX/ActiveX 時常見的坑」;可以讓登錄檔註冊本身變得不必要的選項,整理在「什麼是 Reg-Free COM」。

COM 的世界還有一段歷史淵源。在 Vista/XP 時代,CLSID 等機碼是以「重新導向+反射(兩視圖間同步)」的方式處理,但 Windows 7 廢除了反射機制,現在則是純粹依視圖分離。3 另外,系統上也定義了像 HKLM\SOFTWARE\Wow6432Node\ClassesHKLM\SOFTWARE\Classes\Wow6432Node 這樣的相容用符號連結,但這只是用來拯救那些把 Wow6432Node 寫死在程式碼中的既有應用程式,新開發的應用程式不應該使用。3

另外,即使跨越了登錄檔的分離,DLL 本身的載入還有另一套名稱解析規則在等著。若是「找不到」類的調查,也可以參考「Windows 的 DLL 名稱解析機制」。

7. 故障排除 ── 用 Procmon 觀察「實際讀取的是哪裡」

即使了解機制,面對眼前的故障,要確定「這個處理序到底讀取了哪個實體機碼」,還是需要實際觀察。這時候最強的工具是 Sysinternals 的 Process Monitor(Procmon)。

步驟很簡單。

  1. 啟動 Procmon,在篩選器中加入 Process Name is <目標應用程式>.exe
  2. 在工具列中把顯示範圍限縮為只看登錄檔操作(用 Operation begins with Reg 篩選也可以)
  3. 重現應用程式發生問題的操作,觀察 RegOpenKey / RegQueryValue / RegSetValue 這幾行

重點在於,Procmon 的 Path 欄位所顯示的,是重新導向解析之後的實體路徑。32bit 應用程式即使自認為開啟的是 HKLM\Software\MyApp,Procmon 上顯示出來的會是 HKLM\SOFTWARE\WOW6432Node\MyApp。若這裡出現一整排 NAME NOT FOUND,就能一目了然地知道「是哪個視圖、哪個機碼不存在」;若寫入操作流向了 HKCU\Software\Classes\VirtualStore\...,也能直接觀察到虛擬化的發生。若是在調查 COM 的 0x80040154,甚至可以追蹤到 CLSID\{...} 的開啟失敗,究竟發生在哪一個視圖上。關於 Procmon 篩選器的設計與判讀方式的細節,請參考「Process Monitor(ProcMon)實戰指南」。

8. 實務判斷表

情況 該做的事 理由・補充
「regedit 看得到,應用程式卻看不到」 reg query ... /reg:64/reg:32 分別讀取比對兩個視圖 先確定值到底在哪個視圖5
32bit/64bit 兩種自家處理序都要讀取同一個 HKLM 設定 在寫入端固定視圖(例如 64bit 視圖),讀取端所有人都明確指定同一個視圖 RegistryView.Registry64 / KEY_WOW64_64KEY 統一42
想在程式碼中把 Wow6432Node 寫死 不要這麼做,改用指定視圖的 API 實體位置屬於系統保留區,在 ARM 上會變成 WowAA32Node12
使用者專屬設定該放在哪裡 放在 HKCU(或 AppData) HKCU 是共用機碼,沒有 bitness 問題,也沒有權限問題3
舊版 32bit 應用程式的設定「因使用者而異」 檢查 HKCU\Software\Classes\VirtualStore 這是虛擬化轉送的值因使用者而累積的典型模式6
對舊版 EXE 加上資訊清單/改為 64bit 先盤點 HKLM 寫入的地方再動手 虛擬化會失效,原本「能動」的寫入操作會開始失敗67
COM 的 0x80040154 確認用戶端與伺服器的 bitness,並用對應的 regsvr32/視圖確認註冊狀況 CLSID 註冊是依視圖分離的3
無法確定到底讀取了哪裡 用 Procmon 觀察實體路徑與結果(NAME NOT FOUND 等) 停止猜測,直接觀察事實才是最快的方法

9. 總結

  • 64bit Windows 的登錄檔有兩個視圖,32bit 處理序的 HKLM\Software 會被透明地重新導向到 Wow6432Node。這是「明明寫入了卻沒有值」的第一嫌疑人。
  • 重新導向並非涵蓋所有機碼。請記住這個巢狀結構:Classes 是共用,其下的 CLSID / Interface 是重新導向,HKCU 幾乎全部共用。
  • 切勿把 Wow6432Node 寫死在程式碼中。請用 reg.exe 的 /reg:64 /reg:32、.NET 的 RegistryView、Win32 的 KEY_WOW64_64KEY / KEY_WOW64_32KEY 明確指定視圖。
  • UAC 登錄檔虛擬化,會把沒有資訊清單的 32bit 互動式處理序、因權限不足而寫入 HKLM 的操作,靜默轉送到 VirtualStore。這是拯救舊版應用程式的過渡性技術,新開發的應用程式不應依賴它。
  • 安裝程式與應用程式、COM 伺服器與用戶端,要對齊「使用哪個視圖」。在 64bit 移轉的過渡期,要在設計中納入從舊視圖讀取的遷移邏輯。
  • 猶豫不決時,用 Procmon 觀察實體路徑。與其猜測,不如直接觀察,既快又可靠。

相關文章

相關諮詢領域

合同會社小村軟體處理「明明寫入登錄檔卻讀不到值」「COM 註冊找不到」之類的故障調查、32bit 應用程式・COM 元件資產的 64bit 移轉設計,以及包含裝置連動軟體在內的 Windows 業務應用程式委外開發。

參考連結

  1. Microsoft Learn, Registry Redirector。關於登錄檔重新導向器會為 32bit/64bit 應用程式提供各自不同的邏輯視圖且對應用程式透明、HKEY_LOCAL_MACHINE\Software 會被重新導向到 HKEY_LOCAL_MACHINE\Software\Wow6432Node、實體位置屬於系統保留區且應用程式不應直接存取、Windows 10 on ARM 的 32bit ARM 機碼會對應到 WowAA32Node、%ProgramFiles% 字串的置換,以及反射機制已在 Windows 7/Windows Server 2008 R2 中廢除的說明。  2 3 4 5 6 7 8

  2. Microsoft Learn, Accessing an Alternate Registry View。關於 KEY_WOW64_64KEY(0x0100)與 KEY_WOW64_32KEY(0x0200)的意義、在 RegCreateKeyEx・RegDeleteKeyEx・RegOpenKeyEx 的 samDesired 中指定、同時指定兩個旗標會導致 ERROR_INVALID_PARAMETER、對共用機碼沒有效果、子機碼操作應持續使用相同旗標、列舉所有機碼須分兩趟進行,以及 Wow6432Node/WowAA32Node 屬於保留機碼的說明。  2 3 4 5 6 7 8

  3. Microsoft Learn, Registry Keys Affected by WOW64。關於重新導向機碼與共用機碼的清單(Windows 7 以後,HKLM\SOFTWARE 為重新導向,HKLM\SOFTWARE\Classes 為共用,Classes\CLSID・Interface・DirectShow 等為重新導向,Clients・COM3・OLE・RPC・App Paths・Policies・HKCU\SOFTWARE 等為共用)、子機碼會繼承父機碼行為、HKCR 是 HKLM 與 HKCU 的 Classes 的合併視圖,以及含 Wow6432Node 在內的相容用符號連結是為了拯救既有應用程式、新開發應用程式不應使用的說明。  2 3 4 5 6 7 8 9

  4. Microsoft Learn, RegistryView Enum (Microsoft.Win32)。關於 RegistryView 列舉的 Default(0)・Registry64(256)・Registry32(512)、可透過 OpenBaseKey・OpenRemoteBaseKey・FromHandle 指定視圖,以及在 32bit OS 上要求 64bit 視圖時會回傳 32bit 視圖機碼的說明。  2 3 4

  5. Microsoft Learn, reg query。關於 reg query 指令中 /reg:32 為以 32bit 登錄檔視圖存取機碼、/reg:64 為以 64bit 登錄檔視圖存取機碼的選項說明。  2 3

  6. Microsoft Learn, Registry Virtualization。關於寫入 HKLM\Software 會被重新導向到 HKEY_USERS<User SID>_Classes\VirtualStore\Machine\Software、讀取時會回傳以虛擬儲存區優先的合併視圖、虛擬化只適用於 32bit 互動式處理序且僅限 HKLM\Software 之下、系統管理員可寫入的機碼、64bit 處理序・服務・模擬中的操作・指定 requestedExecutionLevel 的處理序・Classes 等子機碼不受影響,以及這是過渡性相容技術且未來將被移除、應用程式不應依賴它,還有透過 reg flags 控制 REG_KEY_DONT_VIRTUALIZE 等旗標的說明。  2 3 4 5 6 7 8 9 10

  7. Microsoft Learn, Application manifests。關於 requestedExecutionLevel 元素中 asInvoker・requireAdministrator・highestAvailable 的意義、指定 requestedExecutionLevel 節點會停用檔案及登錄檔虛擬化、若需為了向後相容而使用虛擬化則應省略此節點,以及 Visual C++ 連結器預設會在資訊清單中內嵌 asInvoker 的 UAC 片段的說明。  2 3

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

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

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

常見問題

整理諮詢這個主題時常見的問題。

regedit 中看得到值,但從應用程式讀取卻不存在,是為什麼?
典型原因是應用程式與 regedit 所看到的登錄檔視圖不同。64bit Windows 的 regedit 是 64bit 處理序,以 64bit 視圖為基準顯示,連 Wow6432Node(32bit 視圖的實體位置)也一併顯示出來。另一方面,32bit 應用程式開啟 HKLM\Software 時,會被 WOW64 的登錄檔重新導向器導向 Wow6432Node 側,因此只存在於 64bit 視圖的值,對它來說就等於「不存在」。請先確認 regedit 中看到的值的路徑是否含有 Wow6432Node,再用 reg query 的 /reg:64 與 /reg:32 分別讀取比對兩個視圖,可以最快釐清問題。
可以在程式碼中直接指定 Wow6432Node 的路徑來存取嗎?
應該避免。Microsoft 官方文件明確指出,重新導向所指向的實體位置是系統保留區域,將來可能會變更,應用程式不應該直接存取。實際上,在 Windows 10 on ARM 上,32bit ARM 應用程式使用的是另一個名為 WowAA32Node 的實體位置,若把 Wow6432Node 寫死在程式中,在 ARM 環境下就會失效。若想存取其他視圖,請使用 KEY_WOW64_64KEY/KEY_WOW64_32KEY 旗標,或 .NET 的 RegistryView 這類官方提供的手段。
明明寫入的是 HKLM,為什麼值卻出現在 HKCU 的 VirtualStore 中?
這是 UAC 登錄檔虛擬化作用的結果。沒有寫入權限的 32bit 互動式處理序,在資訊清單中沒有指定 requestedExecutionLevel 的情況下寫入 HKLM\Software 之下時,不會直接失敗,而是會被轉送到使用者專屬的虛擬儲存區(HKEY_USERS\<SID>_Classes\VirtualStore\Machine\Software,在 regedit 中會顯示於 HKCU\Software\Classes\VirtualStore 之下)。該處理序自己讀回時,看到的是虛擬儲存區與原本位置的合併視圖,所以表面上看起來運作正常,但從 64bit 處理序或服務端看不到這個值,於是就變成「因使用者而異」的奇怪故障。虛擬化是為了拯救舊版應用程式的過渡性技術,新開發的應用程式不應依賴它。
在 C# 中要如何明確指定登錄檔的 bitness(32bit/64bit 視圖)?
在 RegistryKey.OpenBaseKey 中傳入 RegistryView。指定 RegistryView.Registry64,即使是從 32bit 處理序也能讀寫 64bit 視圖;指定 RegistryView.Registry32,則即使是從 64bit 處理序也能讀寫 32bit 視圖(Wow6432Node 側)。RegistryView.Default 會交由處理序自身的 bitness 決定,像 AnyCPU 建置這種執行環境會影響 bitness 的組態,明確指定要讀取哪個視圖會比較安全。另外,在 32bit OS 上要求 Registry64 時,規格上會回傳 32bit 視圖,因此即使還需要支援 32bit OS,同一份程式碼也能正常運作。
要如何停用登錄檔虛擬化,或確認目前是否處於虛擬化狀態?
在應用程式端,只要在資訊清單中指定 requestedExecutionLevel(即使是 asInvoker 也可以),該處理序的檔案/登錄檔虛擬化就會被停用。在管理端,可以用 reg flags 指令針對個別機碼設定與確認 REG_KEY_DONT_VIRTUALIZE 旗標。若是調查已經在執行中的應用程式,最快的方法是在工作管理員的「UAC 虛擬化」欄位確認各處理序的虛擬化狀態,並檢查 HKCU\Software\Classes\VirtualStore 之下是否累積了被轉送的值。至於根本對策,建議一開始就改成不寫入 HKLM 的設計(使用者專屬的設定改放在 HKCU 或 AppData)。

作者檔案

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

Go Komura

小村軟體有限公司 代表

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

回到部落格一覽