“설치 프로그램이 쓴 라이선스 키를, 앱이 시작할 때 읽지 못한다. 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는 “공유”, 다만 그 아래의CLSID나Interface는 “리다이렉트”라는 중첩 구조입니다.3 - 다른 뷰로의 접근은 Win32에서는
RegOpenKeyEx등의samDesired에KEY_WOW64_64KEY/KEY_WOW64_32KEY를 지정, .NET에서는RegistryKey.OpenBaseKey에RegistryView.Registry64/Registry32를 지정, 명령줄에서는reg.exe의/reg:64//reg:32입니다.245 - 관리자 권한이 없는 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에 관한 이야기”로 읽기·쓰기 양쪽에 작용하고, 가상화는 “권한에 관한 이야기”로 쓰기에만 작용합니다. 혼동하기 쉬우므로 상세히 들어가기 전에 전체상을 한 장으로 파악해 두세요.
flowchart TD
S["프로세스가 HKLM의 Software 아래를 조작한다"] --> B{"프로세스는 32bit인가"}
B -- "32bit" --> R["WOW64 레지스트리 리다이렉트<br/>bitness에 관한 이야기, 읽기 쓰기 양쪽에 작용"]
B -- "64bit" --> N["리다이렉트 없음<br/>있는 그대로의 HKLM Software를 읽고 쓴다"]
R --> RV["실제로 읽고 쓰는 곳은<br/>Wow6432Node 아래 = 32bit 뷰"]
RV --> W{"쓰기 시, 해당 키에 대한<br/>쓰기 권한이 없다"}
N --> W
W -- "매니페스트 없는 32bit 대화형 프로세스" --> V["UAC 레지스트리 가상화<br/>권한에 관한 이야기, 쓰기에만 작용"]
V --> VS["사용자별 VirtualStore로 전송<br/>읽기는 원래 위치와의 병합 뷰"]
W -- "64bit 프로세스, 서비스, 매니페스트 있음" --> E["접근 거부로 순순히 실패한다"]
이 그림의 왼쪽 분기(누가 32bit인가)가 2장·3장, 오른쪽 아래 분기(권한과 매니페스트)가 5장의 내용입니다.
2. WOW64의 레지스트리 리다이렉트 ── 32bit 프로세스는 어디를 보고 있는가
64bit Windows는 32bit 앱을 WOW64라는 서브시스템 위에서 동작시킵니다. 이때 레지스트리 리다이렉터가 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 앱의 리다이렉트 대상이 WowAA32Node라는 별도의 키입니다.12 다른 뷰를 읽고 싶다면 4장의 공식 수단을 사용하세요.
세부적인 이야기로는, WOW64는 32bit 앱이 쓰는 %ProgramFiles%로 시작하는 REG_SZ/REG_EXPAND_SZ 문자열을 %ProgramFiles(x86)%로 치환하는 보정도 수행합니다(대소문자까지 일치한 경우에만).1 또한 Vista/XP 시절에는 32bit/64bit 뷰 사이에서 키를 복사해 동기화하는 “레지스트리 반영(리플렉션)”이 있었지만, 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는 공유로 돌아가고, 다시 그 아래의 CLSID나 Interface는 다시 리다이렉트됩니다. “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 / RegDeleteKeyEx의 samDesired에 KEY_WOW64_64KEY(0x0100) 또는 KEY_WOW64_32KEY(0x0200)를 OR로 지정합니다. 둘 다 동시에 지정하면 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로 연 핸들의 2회 패스로 열거해야 합니다. 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\Classes,HKLM\Software\Microsoft\Windows,HKLM\Software\Microsoft\Windows NT아래도 대상 외
실무상 특히 크게 작용하는 것이 “매니페스트의 유무로 동작이 바뀐다”는 점입니다. Visual Studio의 C++ 링커는 기본값으로 asInvoker의 UAC 프래그먼트를 매니페스트에 삽입하기 때문에7, 요즘 툴체인으로 빌드한 EXE는 처음부터 가상화 대상 외입니다. 가상화를 만나게 되는 것은 매니페스트가 없는 VB6/오래된 Delphi/오래된 VC++로 만든 레거시 EXE를 64bit Windows에서 실행하는 현장, 라는 셈이 됩니다. 반대로 레거시 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 개발에서 빠지기 쉬운 것 - Visual Studio의 32bit / 64bit, 등록, 관리자 권한의 덫을 정리“에, 레지스트리 등록 자체를 불필요하게 만드는 선택지는 “Reg-Free COM이란 무엇인가“에 정리되어 있습니다.
COM의 세계에는 또 하나 역사적인 사정이 있습니다. Vista/XP 시절에는 CLSID 등이 “리다이렉트 + 반영(양쪽 뷰 사이의 동기)”으로 취급되었지만, Windows 7에서 반영이 폐지되어 현재는 순수하게 뷰별 분리가 되었습니다.3 또한 HKLM\SOFTWARE\Wow6432Node\Classes → HKLM\SOFTWARE\Classes\Wow6432Node와 같은 호환용 심볼릭 링크가 몇 가지 정의되어 있는데, 이는 Wow6432Node를 하드코딩해 버린 기존 앱의 구제용이며 신규 앱이 사용해도 되는 것이 아닙니다.3
또한 레지스트리의 분리를 넘어서도 DLL 자체의 로드에는 별도의 이름 해결 규칙이 기다리고 있습니다. “찾을 수 없다” 계열의 조사에서는 “Windows에서 DLL 이름 해결의 메커니즘 - 검색 순서, Known DLLs, API set, SxS를 실무용으로 정리“도 함께 참조하세요.
7. 트러블슈팅 ── Procmon으로 “실제로 어디를 읽었는가”를 본다
구조를 알고 있어도 눈앞의 장애에서 “이 프로세스는 결국 어느 물리적 키를 읽었는가”를 확정하려면 관찰이 필요합니다. 여기서 가장 강력한 것이 Sysinternals의 Process Monitor(Procmon)입니다.
절차는 단순합니다.
- Procmon을 실행하고, 필터로
Process Name is <대상 앱>.exe를 추가 - 툴바에서 레지스트리 조작만 표시하도록 좁힌다(
Operation begins with Reg필터도 가능) - 앱의 문제 조작을 재현하고,
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에서는 WowAA32Node가 된다12 |
| 사용자별 설정의 저장 위치 | 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로 투명하게 리다이렉트됩니다. “분명히 썼는데 값이 없다”의 제1용의자입니다. - 리다이렉트는 모든 키에 적용되지 않습니다.
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 컴포넌트나 OCX / ActiveX 개발에서 빠지기 쉬운 것 - Visual Studio의 32bit / 64bit, 등록, 관리자 권한의 덫을 정리
- Reg-Free COM이란 무엇인가 - 등록 불필요로 COM을 쓰는 구조와, 맞는 장면·맞지 않는 장면
- Windows의 관리자 특권이 필요해지는 것은 언제인가 - UAC, 보호 영역, 설계상의 구분 방법
- Windows 사용자 프로필 입문 - AppData와 NTUSER.DAT
- Windows 앱 데이터 저장 위치를 고르는 방법 ── SQLite / JSON / 레지스트리 / Access 판단표
- Process Monitor(ProcMon) 실전 가이드 ── 「설정이 반영되지 않는다」「ACCESS DENIED」를 10분 만에 특정하기
- Windows에서 DLL 이름 해결의 메커니즘 - 검색 순서, Known DLLs, API set, SxS를 실무용으로 정리
관련 상담 영역
합동회사 코무라소프트는 “레지스트리에 썼을 값을 읽을 수 없다”, “COM 등록을 찾을 수 없다” 같은 오류의 조사, 32bit 앱·COM 컴포넌트 자산의 64bit 이전 설계, 장치 연계 소프트웨어를 포함한 Windows 업무용 앱의 수탁 개발을 다루고 있습니다.
참고 링크
-
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
-
Microsoft Learn, Accessing an Alternate Registry View. KEY_WOW64_64KEY(0x0100)와 KEY_WOW64_32KEY(0x0200)의 의미, RegCreateKeyEx·RegDeleteKeyEx·RegOpenKeyEx의 samDesired로 지정한다는 것, 두 플래그를 동시에 지정하면 ERROR_INVALID_PARAMETER가 된다는 것, 공유 키에는 효과가 없다는 것, 하위 키 조작에서 같은 플래그를 계속 사용해야 한다는 것, 전체 키 열거는 2회 패스로 수행한다는 것, Wow6432Node/WowAA32Node가 예약 키라는 것에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
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
-
Microsoft Learn, RegistryView Enum (Microsoft.Win32). RegistryView 열거체의 Default(0)·Registry64(256)·Registry32(512), OpenBaseKey·OpenRemoteBaseKey·FromHandle로 뷰를 지정할 수 있다는 것, 32bit OS에서 64bit 뷰를 요청한 경우 32bit 뷰의 키가 반환된다는 것에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, reg query. reg query 명령의 /reg:32가 32bit 레지스트리 뷰, /reg:64가 64bit 레지스트리 뷰로 키에 접근하는 옵션이라는 것에 대해. ↩ ↩2 ↩3
-
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
-
Microsoft Learn, Application manifests. requestedExecutionLevel 요소의 asInvoker·requireAdministrator·highestAvailable의 의미, requestedExecutionLevel 노드를 지정하면 파일 및 레지스트리의 가상화가 비활성화된다는 것, 하위 호환을 위해 가상화를 이용하고 싶은 경우에는 이 노드를 생략한다는 것, Visual C++ 링커가 기본값으로 asInvoker의 UAC 프래그먼트를 매니페스트에 삽입한다는 것에 대해. ↩ ↩2 ↩3
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows 앱 데이터 저장 위치를 고르는 방법 ── SQLite / JSON / 레지스트리 / Access 판단표
Windows 데스크톱 앱의 데이터를 어디에·무엇으로 저장할지 정리합니다. AppData/ProgramData 구분, SQLite・JSON 파일・레지스트리・Access(.accdb) 각각의 강점과 함정을 판단표로 정리하고, 손상 대책과 비트 수 ...
멀티스레드 실무 베스트 프랙티스 .NET 편 ── 스레드를 늘리기 전에 정해둘 것
「스레드를 만들면 가끔 죽거나 멈춘다」를 막는 설계의 정석을 .NET/C# 대상으로 정리합니다. 스레드를 직접 만들지 않고 Task에 올라타기, 공유 가변 상태 줄이기, 락의 규율, CancellationToken을 통한 정지 설계, UI 스레드...
WMI/CIM을 C#・PowerShell에서 사용하기 ── 하드웨어 정보 취득・프로세스 감시・원격 조회 실무 가이드
PC 시리얼 번호 취득, 디스크 여유 공간 감시, 프로세스 시작 감지의 정석이 WMI/CIM입니다. Get-CimInstance 등 CIM 계열 명령어의 사용법과 구형 Get-WmiObject로부터의 이전, C#의 System.Management...
MSMQ는 언제까지 쓸 수 있을까 ── 「비권장조차 아닌」 레거시 큐의 이전 판단
MSMQ는 공식 비권장 목록에 없지만, System.Messaging은 .NET Framework에만 존재해 .NET으로의 이전을 가로막습니다. 폐지설과 실제 현황을 사실 관계로 정리하고, 계속 사용할지 이전할지의 판단 기준과 이전 대상의 선택 ...
Windows의 TPM이란 무엇인가 ── 그림으로 이해하는 '키를 밖으로 내보내지 않는 금고'와 측정 부팅
TPM을 그림으로 해설합니다. 키를 칩 밖으로 내보내지 않는 구조, PCR과 측정 부팅, BitLocker·Windows Hello에서의 사용 방식, dTPM·fTPM·Pluton의 차이, Get-Tpm으로 확인하는 방법, 복구 키를 요구받았을 ...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- 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로)로 고치는 것을 권장합니다.