winreg 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +42 -0
- data/LICENSE.txt +21 -0
- data/README.md +283 -0
- data/ext/winreg/extconf.rb +25 -0
- data/ext/winreg/winreg.c +909 -0
- data/lib/winreg/version.rb +5 -0
- data/lib/winreg.rb +795 -0
- metadata +124 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fbe8c4f07ef7324d292aacf40153e5f25e412fea50699cd344b066159c9fc48a
|
|
4
|
+
data.tar.gz: e9cc1596e5a598c6b3c1243dcafc2e504e95e96519053aec5888b04634a88cbf
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b0c0714cd31ca0e62c9cfc141ffde6c9e67cc4f42e645f2787bbed9091aed99f58805dff6df0107eeca06dd6f316fce73662fa70b87a3e3b5347d61f8fdf8ba9
|
|
7
|
+
data.tar.gz: dfc6db4e5ab395ea033b5d691f6df7042e744083954c6bd0a717c04426e2daca3c5335e89c2b2d157502f6c6b0157617b52cf5914820d564cdd5a229b1864ba3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-06-01
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- **Typed reads** — strict `string`/`dword`/`qword`/`multi_string`/`binary`
|
|
8
|
+
readers (and their `?` no-raise-on-missing variants) plus a generic
|
|
9
|
+
`read`/`read?` returning `[type, value]`. REG_SZ/REG_EXPAND_SZ decode to UTF-8
|
|
10
|
+
with at most one trailing NUL stripped (unterminated values round-trip);
|
|
11
|
+
REG_EXPAND_SZ is never auto-expanded; REG_MULTI_SZ decodes to `Array<String>`
|
|
12
|
+
tolerating missing/excess terminators; REG_DWORD/REG_QWORD are size-checked.
|
|
13
|
+
- **Typed writes** — `write_string`, `write_expand_string`, `write_multi_string`
|
|
14
|
+
(correct double-NUL wire format), range-checked `write_dword`/`write_qword`,
|
|
15
|
+
`write_binary`, and the generic `write(name, type, value)`. Embedded NULs and
|
|
16
|
+
empty REG_MULTI_SZ elements are rejected, so the gem never produces a malformed
|
|
17
|
+
value.
|
|
18
|
+
- **Raw escape hatches** — `raw`/`write_raw` move exact bytes under an arbitrary
|
|
19
|
+
type tag, never decoding, for forensic and adversarial data.
|
|
20
|
+
- **Default value** — `nil`/`""` addresses the unnamed default value for every
|
|
21
|
+
read/write/delete/probe.
|
|
22
|
+
- **WOW64 views** — `view: :v64`/`:v32` is a first-class constructor option,
|
|
23
|
+
stored on the `Key` and re-applied consistently to every child open/create,
|
|
24
|
+
`delete_key`, `key?` probe, and `Watch` handle; children cannot override it.
|
|
25
|
+
- **Least-privilege opens** — `access: :read` is `KEY_READ`; writes demand an
|
|
26
|
+
explicit `access: :read_write`; `KEY_ALL_ACCESS` appears nowhere.
|
|
27
|
+
- **Enumeration & metadata** — `each_value`/`each_key`/`value_names`/`key_names`
|
|
28
|
+
(16,383-char value names) and `info` (counts + 100ns-precision
|
|
29
|
+
`last_write_time`).
|
|
30
|
+
- **Subkeys** — handle-relative `open`/`create` and `delete_key`
|
|
31
|
+
(`recursive: true` does a view-aware, interruptible, deepest-first walk).
|
|
32
|
+
- **Change notification** — `Key#watch` returns a `Winreg::Watch` (or loops in
|
|
33
|
+
block form); `wait` returns `:changed`/`:deleted`/`nil`. Registrations are
|
|
34
|
+
`REG_NOTIFY_THREAD_AGNOSTIC` and rearmed before delivery (no final state is
|
|
35
|
+
ever missed); waits release the GVL and are interruptible standalone, and run
|
|
36
|
+
cooperatively under a fiber scheduler (winloop) via a worker-thread offload.
|
|
37
|
+
- Error taxonomy under `Winreg::Error`: `OSError` (with `#code`) and its
|
|
38
|
+
`NotFound`/`AccessDenied`/`KeyDeleted` subclasses, plus `TypeMismatch`,
|
|
39
|
+
`MalformedValue`, and `Closed`.
|
|
40
|
+
|
|
41
|
+
Pure C extension over advapi32 + kernel32 (`rb_raise`/longjmp-safe; every handle
|
|
42
|
+
freed by a TypedData finalizer). Windows MSVC (mswin) Ruby only.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ned
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# winreg
|
|
2
|
+
|
|
3
|
+
**Typed Windows registry access for Ruby — exact wire formats, least-privilege opens, WOW64 views as a first-class option, and change notification that cooperates with a fiber scheduler.**
|
|
4
|
+
|
|
5
|
+
Ruby's bundled `win32/registry` is pure Ruby over Fiddle, and it has accumulated
|
|
6
|
+
a decade of wire-format bugs: it writes REG_MULTI_SZ without the trailing empty
|
|
7
|
+
string (a 22-byte image where 24 is correct), it `NoMethodError`s on
|
|
8
|
+
`read(nil)`/`[nil]` for a key's default value, and it `.chop`s the last
|
|
9
|
+
character off any string whose stored bytes are not NUL-terminated. Its
|
|
10
|
+
enumeration name buffer is 514 chars, so a value name of 515+ (the registry
|
|
11
|
+
allows 16,383) raises. And it does not bind `RegNotifyChangeKeyValue` at all, so
|
|
12
|
+
there is no way to watch a key for changes.
|
|
13
|
+
|
|
14
|
+
`winreg` is a thin MSVC C extension that gets the bytes right. It exposes the
|
|
15
|
+
Win32 registry through a typed, hard-to-misuse API: strict typed readers and
|
|
16
|
+
writers that own serialization (so the wire format is correct by construction),
|
|
17
|
+
raw escape hatches for adversarial data, working default-value access,
|
|
18
|
+
range-checked integers, full-length enumeration, `KEY_READ`-not-`KEY_ALL_ACCESS`
|
|
19
|
+
defaults, 32/64-bit views applied consistently to child operations, and change
|
|
20
|
+
watching.
|
|
21
|
+
|
|
22
|
+
The watch surface releases the GVL and is interruptible standalone, and runs
|
|
23
|
+
**cooperatively** under a fiber scheduler such as
|
|
24
|
+
[winloop](https://rubygems.org/gems/winloop) by offloading the blocking wait to a
|
|
25
|
+
worker thread — so a parked watcher never stalls the loop. Plain registry data
|
|
26
|
+
operations are microsecond-scale local calls; they run inline and never park a
|
|
27
|
+
fiber.
|
|
28
|
+
|
|
29
|
+
## API summary
|
|
30
|
+
|
|
31
|
+
| What | API |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Open / create | `Winreg.open(path, access:, view:)`, `Winreg.create(path, access:, view:)`, `Key#open`, `Key#create` |
|
|
34
|
+
| Typed read | `Key#string` `#dword` `#qword` `#multi_string` `#binary` (+ `?` variants), `#read`/`#read?` → `[type, value]` |
|
|
35
|
+
| Typed write | `Key#write_string` `#write_expand_string` `#write_multi_string` `#write_dword` `#write_qword` `#write_binary`, `#write(name, type, value)`, `#delete_value` |
|
|
36
|
+
| Raw escape hatch | `Key#raw` → `[tag, bytes]`, `Key#write_raw(name, tag, bytes)` |
|
|
37
|
+
| Enumerate | `Key#each_value` `#each_key` `#value_names` `#key_names` |
|
|
38
|
+
| Info | `Key#info` → counts + `last_write_time` |
|
|
39
|
+
| Delete | `Key#delete_value`, `Key#delete_key(name, recursive:)` |
|
|
40
|
+
| Probes | `Key#value?`, `Key#key?` |
|
|
41
|
+
| Views | `view: :default | :v64 | :v32` (inherited by children) |
|
|
42
|
+
| Watch | `Key#watch(subtree:, filter:)` → `Winreg::Watch`, `Watch#wait(timeout:)` |
|
|
43
|
+
| Expand | `Winreg.expand_string(str)`, `Key#string(name, expand: true)` |
|
|
44
|
+
|
|
45
|
+
## Requirements
|
|
46
|
+
|
|
47
|
+
- **Windows 10 or newer** with a native **MSVC (mswin)** Ruby. Not supported on
|
|
48
|
+
MinGW/UCRT. (The change-notification path relies on
|
|
49
|
+
`REG_NOTIFY_THREAD_AGNOSTIC`, which is Windows 8+.)
|
|
50
|
+
- Visual Studio 2017+ / Build Tools with the **Desktop development with C++** workload.
|
|
51
|
+
- x64. arm64-mswin is expected to work (the code is arch-neutral) but is
|
|
52
|
+
untested and unsupported until an arm64-mswin Ruby distribution exists.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
gem install winreg
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Reading typed values
|
|
61
|
+
|
|
62
|
+
No elevation is needed to read most of `HKLM\SOFTWARE`:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
require "winreg"
|
|
66
|
+
|
|
67
|
+
Winreg.open('HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion') do |k|
|
|
68
|
+
k.string("ProductName") # => "Windows 10 ..."
|
|
69
|
+
k.read("CurrentMajorVersionNumber") # => [:dword, 10]
|
|
70
|
+
k.string?("NoSuchValue") # => nil (the ? readers return nil when missing)
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`read` returns `[type, value]` where `type` is a Symbol from `Winreg::TYPES`
|
|
75
|
+
(or the raw Integer tag for types outside the table). Strict typed readers
|
|
76
|
+
(`string`, `dword`, …) raise `Winreg::TypeMismatch` if the stored type differs —
|
|
77
|
+
a wrong type is a bug, not an absence, so even the `?` variants raise on a type
|
|
78
|
+
mismatch (they only swallow "missing").
|
|
79
|
+
|
|
80
|
+
## Writing
|
|
81
|
+
|
|
82
|
+
Writers own serialization, so the wire format is correct by construction
|
|
83
|
+
(double-NUL REG_MULTI_SZ, byte counts including terminators). They require a key
|
|
84
|
+
opened `access: :read_write`:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
Winreg.create('HKCU\Software\Vendor\App') do |k|
|
|
88
|
+
k.write_string("InstallDir", 'C:\Vendor\App')
|
|
89
|
+
k.write_expand_string("Cache", '%LOCALAPPDATA%\Vendor')
|
|
90
|
+
k.write_multi_string("Plugins", %w[alpha beta]) # correct double-NUL wire format
|
|
91
|
+
k.write_dword("Port", 8080)
|
|
92
|
+
|
|
93
|
+
k.multi_string("Plugins") # => ["alpha", "beta"]
|
|
94
|
+
k.dword("Port") # => 8080
|
|
95
|
+
|
|
96
|
+
k.write_dword("Port", -1) # raises RangeError (stdlib silently wraps to 0xFFFFFFFF)
|
|
97
|
+
k.dword("InstallDir") # raises Winreg::TypeMismatch (it is REG_SZ)
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Integers are unsigned and range-checked (`0..2**32-1` / `0..2**64-1`); embedded
|
|
102
|
+
NULs in string data and empty/NUL-containing REG_MULTI_SZ elements are rejected
|
|
103
|
+
with `ArgumentError` — the gem never produces a malformed value.
|
|
104
|
+
|
|
105
|
+
## The default value
|
|
106
|
+
|
|
107
|
+
The unnamed (default) value is addressed by `nil` or `""` for every operation —
|
|
108
|
+
the case where the stdlib raises `NoMethodError`:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
Winreg.open('HKCU\Software\Classes\CLSID') do |k|
|
|
112
|
+
k.read?(nil) # => [:sz, "..."] or nil
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Expand strings
|
|
117
|
+
|
|
118
|
+
REG_EXPAND_SZ values are returned **literally** — the `%VAR%` text and the true
|
|
119
|
+
`:expand_sz` type are preserved, never silently expanded or masked to `:sz`.
|
|
120
|
+
Expansion is explicit and uses `ExpandEnvironmentStringsW` (not a Ruby gsub over
|
|
121
|
+
`ENV`, whose semantics differ — unknown variables are left literal exactly as the
|
|
122
|
+
OS defines):
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
k.string("Cache") # => "%LOCALAPPDATA%\\Vendor" (unexpanded)
|
|
126
|
+
k.string("Cache", expand: true) # => "C:\\Users\\me\\AppData\\Local\\Vendor"
|
|
127
|
+
Winreg.expand_string('%TEMP%\\x') # => "C:\\Users\\me\\AppData\\Local\\Temp\\x"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Enumerating
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
Winreg.open('HKCU\Software\Vendor\App') do |k|
|
|
134
|
+
k.each_value { |name, type, value| puts "#{name} (#{type}) = #{value.inspect}" }
|
|
135
|
+
k.value_names # => ["InstallDir", "Cache", "Plugins", "Port"]
|
|
136
|
+
k.key_names # => subkey names
|
|
137
|
+
|
|
138
|
+
info = k.info
|
|
139
|
+
info.value_count # => Integer
|
|
140
|
+
info.last_write_time # => Time (100ns FILETIME precision)
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Enumeration is live and snapshot-less; kernel order is arbitrary and concurrent
|
|
145
|
+
mutation may skip or repeat entries. `each_value` decodes data and so can raise
|
|
146
|
+
`MalformedValue` on a hostile value mid-iteration — use `value_names` + `raw` for
|
|
147
|
+
forensic robustness (it never decodes).
|
|
148
|
+
|
|
149
|
+
## WOW64 views
|
|
150
|
+
|
|
151
|
+
A 32/64-bit view is a constructor option, stored on the key and **automatically
|
|
152
|
+
re-applied** to every child `open`/`create`, `delete_key`, `key?` probe, and
|
|
153
|
+
`Watch`. Children cannot override it — this is the API encoding of the
|
|
154
|
+
view-consistency rule, so you never accidentally cross the redirection boundary:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
Winreg.create('HKCU\Software\Classes\CLSID\{...}', view: :v64) do |k|
|
|
158
|
+
child = k.create("Sub") # also :v64, no way to ask for :v32 here
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Avoid addressing `Wow6432Node` literally; use `view:` instead.
|
|
163
|
+
|
|
164
|
+
## Watching for changes
|
|
165
|
+
|
|
166
|
+
`Key#watch` returns an armed `Winreg::Watch`, or loops in block form. The block
|
|
167
|
+
form yields `:changed` per coalesced change and yields `:deleted` once (then
|
|
168
|
+
returns) when the watched key is deleted:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
Winreg.create('HKCU\Software\Vendor\App') do |k|
|
|
172
|
+
k.watch(filter: :values) do |event|
|
|
173
|
+
case event
|
|
174
|
+
when :changed then reload_config!
|
|
175
|
+
when :deleted then break
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The primitive form takes a timeout (seconds; `nil` = infinite):
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
w = key.watch(subtree: true)
|
|
185
|
+
case w.wait(timeout: 5)
|
|
186
|
+
when :changed then puts "something under #{key.path} changed"
|
|
187
|
+
when :deleted then puts "key is gone"
|
|
188
|
+
when nil then puts "no change in 5s"
|
|
189
|
+
end
|
|
190
|
+
w.close
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`filter:` is a Symbol or Array of `:values`, `:keys`, `:attributes`, `:security`,
|
|
194
|
+
`:default` (= keys + values), or `:all`. Notifications **coalesce** (`:changed`
|
|
195
|
+
means "≥ 1 matching change since the previous delivery") and carry **no
|
|
196
|
+
payload** — no value name, no change kind. The contract is "something changed; go
|
|
197
|
+
look"; diff it yourself (e.g. compare `info.last_write_time` snapshots). The
|
|
198
|
+
registration is rearmed *before* each delivery, so the final state is never
|
|
199
|
+
missed; the cost is that one spurious wakeup is possible. The `Watch` opens its
|
|
200
|
+
own private `KEY_NOTIFY` handle, so closing the originating `Key` does not disturb
|
|
201
|
+
it.
|
|
202
|
+
|
|
203
|
+
### Fiber schedulers (winloop)
|
|
204
|
+
|
|
205
|
+
`Watch#wait` is the only blocking call. Under a live `Fiber.scheduler` such as
|
|
206
|
+
[winloop](https://rubygems.org/gems/winloop) the native wait is offloaded to a
|
|
207
|
+
worker thread and `Thread#value` parks the calling fiber through the scheduler's
|
|
208
|
+
hooks, so the loop keeps serving other fibers:
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
require "winloop"
|
|
212
|
+
Winloop.run do
|
|
213
|
+
Fiber.schedule do
|
|
214
|
+
Winreg.create('HKCU\Software\Vendor\App') do |k|
|
|
215
|
+
k.watch(filter: :values) { |e| break if e == :deleted }
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
Fiber.schedule { do_other_io }
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
With no scheduler the same call just blocks the calling thread (releasing the GVL,
|
|
223
|
+
interruptible by `Thread#kill` / `Ctrl-C` / `Timeout`). Caveat: a fiber unwound
|
|
224
|
+
between the worker observing `:changed` and value delivery loses that one
|
|
225
|
+
delivery — harmless, because `:changed` is stateless and the registration stays
|
|
226
|
+
armed, so the next change still fires.
|
|
227
|
+
|
|
228
|
+
## Raw escape hatches
|
|
229
|
+
|
|
230
|
+
`raw`/`write_raw` move exact bytes under an arbitrary type tag and never decode,
|
|
231
|
+
for adversarial data or types outside the typed surface:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
tag, bytes = k.raw("Plugins") # => [7, "a\x00l\x00...\x00\x00\x00\x00"]
|
|
235
|
+
k.write_raw("Custom", 8, bytes) # arbitrary type tag (REG_RESOURCE_LIST here)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Errors
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
StandardError
|
|
242
|
+
└─ Winreg::Error
|
|
243
|
+
├─ Winreg::OSError # a Windows API failed; #code is the LSTATUS / Win32 code
|
|
244
|
+
│ ├─ Winreg::NotFound # ERROR_FILE_NOT_FOUND (2)
|
|
245
|
+
│ ├─ Winreg::AccessDenied # ERROR_ACCESS_DENIED (5)
|
|
246
|
+
│ └─ Winreg::KeyDeleted # ERROR_KEY_DELETED (1018): the handle is valid, the key is gone
|
|
247
|
+
├─ Winreg::TypeMismatch # a typed reader's stored type differs
|
|
248
|
+
├─ Winreg::MalformedValue # stored bytes don't decode as the claimed type
|
|
249
|
+
└─ Winreg::Closed # an operation on a closed Key or Watch
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Plain argument mistakes raise Ruby's own `ArgumentError` / `TypeError` /
|
|
253
|
+
`RangeError` (e.g. an integer out of range) — those are misuse, not OS state.
|
|
254
|
+
|
|
255
|
+
## Notes & limitations
|
|
256
|
+
|
|
257
|
+
- **Notifications coalesce and carry no payload.** A single `:changed` may stand
|
|
258
|
+
for many changes; you get a symbol, not what changed. Diff it yourself.
|
|
259
|
+
- **`RegRestoreKey` deletion is not detected.** A key replaced via
|
|
260
|
+
`RegRestoreKey` does not surface as `:deleted` (an OS limitation).
|
|
261
|
+
- **Names with invalid UTF-16 are lenient-decoded.** Enumeration never aborts on
|
|
262
|
+
a hostile name; such names come back with U+FFFD replacement and therefore
|
|
263
|
+
cannot be round-trip addressed through the gem (a deliberate v1 trade — the
|
|
264
|
+
alternative makes a whole key unenumerable).
|
|
265
|
+
- **Embedded NULs in stored string data are preserved**, not silently truncated
|
|
266
|
+
to the Win32-consumer view (`"a\0b"` reads as `"a\0b"`); writers reject embedded
|
|
267
|
+
NULs so the gem never creates such a value.
|
|
268
|
+
- **Forward slash is a legal key-name character.** The path parser splits on
|
|
269
|
+
backslash only and never translates `/`.
|
|
270
|
+
- **Keep values ≤ ~2 KiB** per Microsoft guidance (not enforced; the only hard
|
|
271
|
+
limit is the 4 GiB DWORD byte-count guard).
|
|
272
|
+
- **HKLM writes need elevation.** 64-bit processes are never virtualized, so a
|
|
273
|
+
non-elevated HKLM write surfaces as an honest `Winreg::AccessDenied`.
|
|
274
|
+
- **Remote registry is unsupported.** A path starting with `\\server\…` raises
|
|
275
|
+
`ArgumentError` at parse time.
|
|
276
|
+
- Every kernel handle lives in a wrapper whose finalizer closes it, so a
|
|
277
|
+
forgotten `#close` never leaks — but closing explicitly (or using the block
|
|
278
|
+
forms) is good manners.
|
|
279
|
+
- **Windows/MSVC only.** Links `advapi32` + `kernel32`, built with `cl.exe`.
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
[MIT](LICENSE.txt).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# extconf.rb for the winreg extension — typed Windows registry access and
|
|
4
|
+
# change notification (RegNotifyChangeKeyValue).
|
|
5
|
+
|
|
6
|
+
require "mkmf"
|
|
7
|
+
|
|
8
|
+
unless RbConfig::CONFIG["target_os"] =~ /mswin/
|
|
9
|
+
abort <<~MSG
|
|
10
|
+
winreg requires a native Windows MSVC (mswin) Ruby — it binds the Win32
|
|
11
|
+
registry (RegOpenKeyExW/RegSetValueExW/RegNotifyChangeKeyValue) and is
|
|
12
|
+
built with cl.exe. Your Ruby is "#{RbConfig::CONFIG['arch']}".
|
|
13
|
+
MSG
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# System import libs (bare "NAME.lib" tokens on mswin):
|
|
17
|
+
# advapi32 - registry APIs (RegOpenKeyExW/RegQueryValueExW/RegSetValueExW/
|
|
18
|
+
# RegEnumValueW/RegEnumKeyExW/RegQueryInfoKeyW/RegDeleteKeyExW/
|
|
19
|
+
# RegDeleteValueW/RegNotifyChangeKeyValue)
|
|
20
|
+
# kernel32 (events, WaitForMultipleObjects, ExpandEnvironmentStringsW) is
|
|
21
|
+
# linked by default. Pure C — no -EHsc, so rb_raise/longjmp is the normal,
|
|
22
|
+
# safe error mechanism.
|
|
23
|
+
$libs = [$libs, "advapi32.lib"].join(" ")
|
|
24
|
+
|
|
25
|
+
create_makefile("winreg/winreg")
|