11 레지스트리, 환경 변수, INI 파일, 파일 형식과 링크
목표: 다른 프로그램이 찾는 설정을 Hello 에 준다 - 모든 형식의 레지스트리 값, 환경 변수, INI 파일의 한 줄 - 그리고 Windows 가 .hello 파일과 hello: 링크를 Hello 로 열게 한다.
11.1 2분 만에 보는 레지스트리#
Windows 레지스트리는 폴더처럼 짜인 설정 데이터베이스다: 키는 다른 키와 값을 담고, 값에는 이름, 형식, 데이터가 있다. 맨 위 키("루트")는:
| 루트 | 담는 것 |
|---|---|
HKLM(HKEY_LOCAL_MACHINE) | 컴퓨터 전체의 설정; 쓰려면 관리자 권한이 필요하다 |
HKCU(HKEY_CURRENT_USER) | 지금 사용자의 설정 |
HKCR(HKEY_CLASSES_ROOT) | 파일 형식과 링크(둘을 합쳐 보인 것) |
HKMU | rubrapack(과 Windows Installer)의 "모든 사용자용이면 HKLM, 한 사용자용이면 HKCU" |
Windows 에서 regedit 를 실행하면 둘러볼 수 있다(모르는 것은 바꾸지 않는다).
11.2 원본#
# tutorial 11: hello.toml
format = 1
[package]
name = "Hello"
manufacturer = "Example Software"
version = "$(VERSION)"
arch = "x64"
upgrade-code = "{3F2A6C1D-8B4E-4F7A-9C2D-5E6F7A8B9C0D}"
[define]
VERSION = "1.9.0"
[dir.INSTALLDIR]
path = "$(ProgramFiles)/Hello"
[file.Hello]
dir = "INSTALLDIR"
source = "dist/hello.exe"
[file.Settings]
dir = "INSTALLDIR"
source = "dist/settings.ini"
[registry.InstallDir]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "InstallDir"
value = "[INSTALLDIR]"
[registry.Greeting]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "Greeting"
value = "Hello, world"
with = "file:Hello"
[registry.Runs]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "MaxRuns"
type = "dword"
value = 100
[registry.BigNumber]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "Limit"
type = "qword"
value = "0x100000000"
[registry.Colors]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "Colors"
type = "multi"
value = ["red", "green", "blue"]
[registry.LogPath]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "LogPath"
type = "expand"
value = "%TEMP%\\hello.log"
[registry.Legacy32]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "Mode"
value = "compatible"
view = "32"
[registry.OldKey]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello Old'
remove = true
[registry.UserChoice]
root = "HKLM"
key = 'SOFTWARE\Example Software\Hello'
name = "FirstRun"
type = "dword"
value = 1
keep = true
[env.HelloHome]
name = "HELLO_HOME"
value = "[INSTALLDIR]"
[env.Path]
name = "PATH"
value = "[INSTALLDIR]"
mode = "append"
[ini.InstalledVersion]
dir = "INSTALLDIR"
file = "settings.ini"
section = "hello"
key = "installed"
value = "[ProductVersion]"
[ini.Plugins]
dir = "INSTALLDIR"
file = "settings.ini"
section = "hello"
key = "plugins"
value = "core"
mode = "add"
[assoc.HelloDoc]
extension = ".hello"
prog-id = "Example.HelloDocument"
description = "Hello document"
target = "file:Hello"
icon = "file:Hello"
args = "--open \"%1\""
[protocol.HelloLink]
name = "hello"
description = "Hello link"
target = "file:Hello"
TOML 메모: 작은따옴표 문자열은 쓴 그대로 쓰이므로 'SOFTWARE\Example Software\Hello' 에는 백슬래시를 겹쳐 쓸 필요가 없다. 큰따옴표 안에서는 백슬래시가 이스케이프를 시작한다("%TEMP%\\hello.log").
11.3 레지스트리 값: [registry.ID]#
type | TOML 값 | 레지스트리 형식 |
|---|---|---|
string(기본) | 문자열 | REG_SZ |
expand | %변수% 가 든 문자열 | REG_EXPAND_SZ - 값을 읽을 때 Windows 가 %TEMP% 를 펼친다 |
dword | 정수 0 .. 4294967295 | REG_DWORD, 32비트 |
qword | 정수, 또는 "0x" 와 16진수 16자리까지 | REG_QWORD, 64비트 |
binary | 16진수, "01ff" | REG_BINARY |
multi | 문자열 배열 | REG_MULTI_SZ |
name이 없으면: 키의 기본값(regedit 에서 "(기본값)").value는 서식 문자열이다:[INSTALLDIR]은 설치 폴더,[#Hello]는 파일Hello의 경로가 된다.- 모든 값은 제품과 함께 다시 지워진다.
keep = true는 남긴다(여기서는 프로그램이 바꿀 수 있는FirstRun). with = "file:Hello"는 값을 그 파일에 묶는다: 파일과 함께 설치되고 지워진다(Greeting).view = "32": 64비트 Windows 는 32비트 프로그램을 위한 레지스트리를 따로 둔다.SOFTWARE\Example Software\Hello를 읽는 32비트 프로그램은 그쪽을 본다. x64 패키지의 값은view = "32"가 아니면 64비트 쪽으로 간다.remove = true는 설치하는 동안name이 가리키는 값을 -name이 없으면 키 전체를 - 지운다(OldKey: 옛 판이 남긴 것).
Windows Installer 는 REG_QWORD 를 스스로 쓰지 못한다. qword 값을 위해 rubrapack 은 작은 도우미 DLL 을 패키지에 더하고, 이것이 값을 쓰고 설치가 실패하면 이전 값을 되돌린다.
11.4 환경 변수: [env.ID]#
컴퓨터 전체의 변수(사용자별 설치에서는 사용자의 것)다. mode = "set"(기본)은 정하고, append 는 있는 것의 끝에 ;값 을, prepend 는 앞에 값; 을 더한다. 제품을 지우면 정확히 그만큼 되돌린다: HELLO_HOME 은 지워지고, PATH 에서는 ;C:\Program Files\Hello\ 만 빠진다. keep = true 는 남긴다. 설치 뒤에 시작한 프로그램이 바뀐 것을 본다(이미 열려 있던 터미널은 못 본다).
11.5 INI 파일: [ini.ID]#
INI 파일은 [섹션] 과 키=값 줄로 된 글 파일이다. mode = "set"(기본)은 섹션에 키=값 을 쓰고, add 는 값을 쉼표로 나눈 목록에 더하고(plugins=core, 또는 plugins=extra 가 있었다면 plugins=extra,core), remove 는 키를 지운다 - 파일을 설치하기 전에 하므로 옛 판이 남긴 INI 파일을 치운다. 제거는 set 과 add 가 쓴 것을 빼낸다. file 은 dir 안의 파일 이름이며, 패키지가 설치하는 파일이 아니어도 된다.
설치 뒤 settings.ini 는:
[hello]
greeting=Hello
installed=1.9.0
plugins=core
11.6 파일 형식: [assoc.ID]#
[assoc.HelloDoc] 는 Windows 가 .hello 파일을 Hello 로 열게 한다:
extension은 점을 포함한 소문자 파일 끝이다.prog-id는 문서 종류의 이름이다(회사.종류). 여러 확장자가 하나를 함께 쓸 수 있다.description은 탐색기가 그 형식을 부르는 이름("Hello document")이다.target은 프로그램,args는 인자다("%1"은 파일 경로이며 기본값이다).icon(file:ID)은 문서가 보일 첫 아이콘을 가진 파일이다.content-type("text/plain"같은 미디어 형식)과perceived-type("text","image", ...)은 그 파일에 어떤 데이터가 들었는지 Windows 에 알린다.default = false면 프로그램을 "연결 프로그램"에만 올리고, 형식은 지금 가진 쪽에 그대로 둔다.
사용자가 .hello 에 이미 다른 프로그램을 골라 두었다면 Windows 는 그 선택을 지킨다.
11.7 링크: [protocol.ID]#
[protocol.HelloLink] 는 hello: 방식을 등록한다. 브라우저나 실행 상자의 hello:world 링크가 링크 전체를 인자로 Hello 를 시작한다.
11.8 우클릭 메뉴: [menu.ID]#
파일에 무언가를 하는 프로그램 - 변환한다, 검사한다, 폴더를 자기 안에서 연다 - 은 탐색기의 우클릭 메뉴에 자리가 있어야 한다. Hello 에는 그런 명령이 없지만, 있다면 이것이 전부다:
[menu.Greet]
on = [".hello", "folder"] # .hello 파일과 폴더에서
text = "Greet with Hello"
text-ko = "Hello 로 인사"
target = "file:HelloExe"
args = "--greet \"%1\"" # "%1" 은 우클릭한 것
on은 항목이 나오는 자리다: 파일 형식, 모든 파일인"*","folder","background"(열린 폴더의 빈 곳),"drive", 또는 어느[assoc.*]의 형식인"assoc:HelloDoc".text는 메뉴에 보이는 문구다.text-ko같은 키로 다른 언어의 문구를 준다.target과args는 파일 형식에서처럼 프로그램과 그 인자다.target이 없는[menu.*]는 하위 메뉴이고, 항목들은parent로 그것을 가리킨다.multi = "single"이면 하나를 골랐을 때만 나온다.extended = true면 Shift 를 누른 채일 때만 나온다.windows11 = false면 Windows 11 메뉴에는 넣지 않는다.
Windows 에는 이런 메뉴가 둘 있고, 이 표가 둘 다 채운다. 옛 메뉴(Windows 10, Windows 11 의 "추가 옵션 표시")는 레지스트리를 읽고, 패키지가 그것을 적는다. Windows 11 메뉴는 신원이 있는 패키지의 항목만 받고 항목마다 작은 COM 클래스가 맡아야 한다 - rubrapack 이 그 클래스(메뉴 부품)를 가져오고, MSI 라면 프로그램 옆에 아주 작은 신원 패키지를 설치해 등록한다. 직접 쓸 코드도, 살 인증서도 없다. 자세한 이야기는 참조의 "탐색기의 우클릭 메뉴"에 있다.
11.9 COM 클래스: [com.ID]#
다른 프로그램이 클래스 ID 로, 또는 스크립트에서 Hello.Widget 같은 이름으로 객체를 만들어 쓰는 프로그램이나 DLL 은 COM 클래스로 등록해야 한다. Hello 에는 없지만, 패키지에 DLL widget.dll 이 있다면 이것으로 충분하다:
[com.Widget]
file = "file:WidgetDll"
class = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D35}" # rubrapack guid 가 하나 만들어 준다
description = "Hello widget"
prog-id = "Hello.Widget"
threading = "both"
file은 서버다: 호출한 쪽에 올라가는.dll, 또는 시작되는.exe(args가 그 인자이며 흔히-Embedding).class는 서버가 응답하는 클래스 ID,prog-id는 그것을 부르는 읽기 쉬운 이름이다.threading은 DLL 의 아파트다:sta(기본),mta,both,neutral.app-id와surrogate = true는 DLL 이 별도 프로세스(dllhost)에서 돌 수 있게 하고,typelib,typelib-version,typelib-file은 형식 라이브러리를 함께 등록한다.
그러면 PowerShell 이 New-Object -ComObject Hello.Widget 으로 객체를 하나 만든다. 제거하면 등록도 풀린다.
DLL 클래스는 파일 형식의 탐색기 처리기도 될 수 있다 - 탐색기가 .hello 파일에 보이는 그림, 미리 보기 창에 보이는 것, 나열하는 속성:
[handler.HelloThumbs]
kind = "thumbnail" # 또는 "preview", "property"
class = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D41}" # DLL 의 [com.*] 클래스
types = [".hello"]
description 은 미리 보기 처리기의 이름이다. 속성 처리기는 컴퓨터별 패키지가 필요하다. MSIX 에서는 썸네일과 미리 보기 처리기는 동작하지만(미리 보기 처리기의 클래스는 threading = "sta") 속성 처리기는 동작하지 않으므로, 거기서 속성 처리기는 msi-only = true 를 붙인다.
11.10 해 보기#
설치한 뒤:
regedit:HKEY_LOCAL_MACHINE\SOFTWARE\Example Software\Hello에 값들이 있고,...\WOW6432Node\Example Software\Hello에Mode(32비트 쪽)가 있다.- 새 터미널:
echo %HELLO_HOME%과echo %PATH%. C:\Program Files\Hello\settings.ini: 새 두 줄.- 바탕화면의
test.hello파일이 Hello 로 열리고, Win+R 에hello:world, Enter 로 Hello 가 시작된다.
Hello 를 지우면 FirstRun 말고는 모두 사라진다.
11.11 안에서 무슨 일이 일어났나#
C:\work\hello> rubrapack inspect hello.msi Registry
...
Colors 2 SOFTWARE\Example Software\Hello Colors [~]red[~]green[~]blue[~] C_f023...
Greeting 2 SOFTWARE\Example Software\Hello Greeting Hello, world C_185f...
HelloDoc.Ext 0 .hello Example.HelloDocument C_185f...
LogPath 2 SOFTWARE\Example Software\Hello LogPath #%%TEMP%\hello.log C_2e80...
Runs 2 SOFTWARE\Example Software\Hello MaxRuns #100 C_97ef...
...
둘째 열은 루트다(0 HKCR, 1 HKCU, 2 HKLM, -1 HKMU). 값 열은 Windows Installer 가 원하는 대로 첫 글자에 형식을 싣는다: #100 은 DWORD, #% 는 펼치는 문자열, [~] 는 여러 문자열을 가르고, #x 는 이진 데이터를 시작한다. 파일 형식과 링크는 프로그램 컴포넌트 안의 HKCR 아래 레지스트리 값일 뿐이다. QWORD 값은 이 표에 없다: 도우미 DLL 을 위한 속성 RP_QWORDS 에 실려 간다. 환경 변수는 Environment 표에 있다:
HelloHome =-*HELLO_HOME [INSTALLDIR] C_0fa9...
Path =-*PATH [~];[INSTALLDIR] C_82df...
이름에서 = 는 "정하기", - 는 "제거 때 지우기", * 는 "시스템 변수"다. 값의 [~] 는 원래 있던 것을 뜻한다.