]>
Commit | Line | Data |
---|---|---|
b4c522fa IB |
1 | /** |
2 | This library provides Win32 Registry facilities. | |
3 | ||
4 | Copyright: Copyright 2003-2004 by Matthew Wilson and Synesis Software | |
5 | Written by Matthew Wilson | |
6 | ||
7 | License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). | |
8 | ||
9 | Author: Matthew Wilson, Kenji Hara | |
10 | ||
11 | History: | |
12 | Created 15th March 2003, | |
13 | Updated 25th April 2004, | |
14 | ||
5fee5ec3 | 15 | Source: $(PHOBOSSRC std/windows/registry.d) |
b4c522fa IB |
16 | */ |
17 | /* ///////////////////////////////////////////////////////////////////////////// | |
18 | * | |
19 | * This software is provided 'as-is', without any express or implied | |
20 | * warranty. In no event will the authors be held liable for any damages | |
21 | * arising from the use of this software. | |
22 | * | |
23 | * Permission is granted to anyone to use this software for any purpose, | |
24 | * including commercial applications, and to alter it and redistribute it | |
25 | * freely, in both source and binary form, subject to the following | |
26 | * restrictions: | |
27 | * | |
28 | * - The origin of this software must not be misrepresented; you must not | |
29 | * claim that you wrote the original software. If you use this software | |
30 | * in a product, an acknowledgment in the product documentation would be | |
31 | * appreciated but is not required. | |
32 | * - Altered source versions must be plainly marked as such, and must not | |
33 | * be misrepresented as being the original software. | |
34 | * - This notice may not be removed or altered from any source | |
35 | * distribution. | |
36 | * | |
37 | * ////////////////////////////////////////////////////////////////////////// */ | |
38 | module std.windows.registry; | |
39 | version (Windows): | |
40 | ||
5fee5ec3 | 41 | import core.sys.windows.winbase, core.sys.windows.windef, core.sys.windows.winreg; |
b4c522fa IB |
42 | import std.array; |
43 | import std.conv; | |
44 | import std.exception; | |
45 | import std.internal.cstring; | |
46 | import std.internal.windows.advapi32; | |
47 | import std.system : Endian, endian; | |
48 | import std.windows.syserror; | |
49 | ||
50 | //debug = winreg; | |
51 | debug(winreg) import std.stdio; | |
52 | ||
53 | private | |
54 | { | |
55 | import core.sys.windows.winbase : lstrlenW; | |
56 | ||
57 | void enforceSucc(LONG res, lazy string message, string fn = __FILE__, size_t ln = __LINE__) | |
58 | { | |
59 | if (res != ERROR_SUCCESS) | |
60 | throw new RegistryException(message, res, fn, ln); | |
61 | } | |
62 | } | |
63 | ||
64 | /* ************* Exceptions *************** */ | |
65 | ||
66 | // Do not use. Left for compatibility. | |
67 | class Win32Exception : WindowsException | |
68 | { | |
69 | @safe | |
70 | this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) | |
71 | { | |
72 | super(0, message, fn, ln); | |
73 | } | |
74 | ||
75 | @safe | |
76 | this(string message, int errnum, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) | |
77 | { | |
78 | super(errnum, message, fn, ln); | |
79 | } | |
80 | ||
81 | @property int error() { return super.code; } | |
82 | } | |
83 | ||
5fee5ec3 | 84 | version (StdUnittest) import std.string : startsWith, endsWith; |
b4c522fa IB |
85 | |
86 | @safe unittest | |
87 | { | |
88 | // Test that we can throw and catch one by its own type | |
89 | string message = "Test W1"; | |
90 | ||
91 | auto e = collectException!Win32Exception( | |
92 | enforce(false, new Win32Exception(message))); | |
93 | assert(e.msg.startsWith(message)); | |
94 | } | |
95 | ||
96 | @system unittest | |
97 | { | |
98 | // ditto | |
99 | string message = "Test W2"; | |
100 | int code = 5; | |
101 | ||
102 | auto e = collectException!Win32Exception( | |
103 | enforce(false, new Win32Exception(message, code))); | |
104 | assert(e.error == code); | |
105 | assert(e.msg.startsWith(message)); | |
106 | } | |
107 | ||
108 | /** | |
109 | Exception class thrown by the std.windows.registry classes. | |
110 | */ | |
111 | class RegistryException | |
112 | : Win32Exception | |
113 | { | |
114 | public: | |
115 | /** | |
116 | Creates an instance of the exception. | |
117 | ||
118 | Params: | |
119 | message = The message associated with the exception. | |
120 | */ | |
121 | @safe | |
122 | this(string message, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) | |
123 | { | |
124 | super(message, fn, ln, next); | |
125 | } | |
126 | ||
127 | /** | |
128 | Creates an instance of the exception, with the given. | |
129 | ||
130 | Params: | |
131 | message = The message associated with the exception. | |
132 | error = The Win32 error number associated with the exception. | |
133 | */ | |
134 | @safe | |
135 | this(string message, int error, string fn = __FILE__, size_t ln = __LINE__, Throwable next = null) | |
136 | { | |
137 | super(message, error, fn, ln, next); | |
138 | } | |
139 | } | |
140 | ||
141 | @system unittest | |
142 | { | |
143 | // (i) Test that we can throw and catch one by its own type | |
144 | string message = "Test 1"; | |
145 | int code = 3; | |
146 | ||
147 | auto e = collectException!RegistryException( | |
148 | enforce(false, new RegistryException(message, code))); | |
149 | assert(e.error == code); | |
150 | assert(e.msg.startsWith(message)); | |
151 | } | |
152 | ||
153 | @safe unittest | |
154 | { | |
155 | // ditto | |
156 | string message = "Test 2"; | |
157 | ||
158 | auto e = collectException!RegistryException( | |
159 | enforce(false, new RegistryException(message))); | |
160 | assert(e.msg.startsWith(message)); | |
161 | } | |
162 | ||
163 | /* ************* public enumerations *************** */ | |
164 | ||
165 | /** | |
166 | Enumeration of the recognised registry access modes. | |
167 | */ | |
168 | enum REGSAM | |
169 | { | |
170 | KEY_QUERY_VALUE = 0x0001, /// Permission to query subkey data | |
171 | KEY_SET_VALUE = 0x0002, /// Permission to set subkey data | |
172 | KEY_CREATE_SUB_KEY = 0x0004, /// Permission to create subkeys | |
173 | KEY_ENUMERATE_SUB_KEYS = 0x0008, /// Permission to enumerate subkeys | |
174 | KEY_NOTIFY = 0x0010, /// Permission for change notification | |
175 | KEY_CREATE_LINK = 0x0020, /// Permission to create a symbolic link | |
176 | KEY_WOW64_32KEY = 0x0200, /// Enables a 64- or 32-bit application to open a 32-bit key | |
177 | KEY_WOW64_64KEY = 0x0100, /// Enables a 64- or 32-bit application to open a 64-bit key | |
178 | KEY_WOW64_RES = 0x0300, /// | |
179 | KEY_READ = (STANDARD_RIGHTS_READ | |
180 | | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) | |
181 | & ~(SYNCHRONIZE), | |
182 | /// Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE, | |
183 | /// KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY access rights | |
184 | KEY_WRITE = (STANDARD_RIGHTS_WRITE | |
185 | | KEY_SET_VALUE | KEY_CREATE_SUB_KEY) | |
186 | & ~(SYNCHRONIZE), | |
187 | /// Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE, | |
188 | /// and KEY_CREATE_SUB_KEY access rights | |
189 | KEY_EXECUTE = KEY_READ & ~(SYNCHRONIZE), | |
190 | /// Permission for read access | |
191 | KEY_ALL_ACCESS = (STANDARD_RIGHTS_ALL | |
192 | | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | |
193 | | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) | |
194 | & ~(SYNCHRONIZE), | |
195 | /// Combines the KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, | |
196 | /// KEY_NOTIFY, KEY_CREATE_SUB_KEY, KEY_CREATE_LINK, and | |
197 | /// KEY_SET_VALUE access rights, plus all the standard | |
198 | /// access rights except SYNCHRONIZE | |
199 | } | |
200 | ||
201 | /** | |
202 | Enumeration of the recognised registry value types. | |
203 | */ | |
204 | enum REG_VALUE_TYPE : DWORD | |
205 | { | |
206 | REG_UNKNOWN = -1, /// | |
207 | REG_NONE = 0, /// The null value type. (In practise this is treated as a zero-length binary array by the Win32 registry) | |
208 | REG_SZ = 1, /// A zero-terminated string | |
209 | REG_EXPAND_SZ = 2, /// A zero-terminated string containing expandable environment variable references | |
210 | REG_BINARY = 3, /// A binary blob | |
211 | REG_DWORD = 4, /// A 32-bit unsigned integer | |
212 | REG_DWORD_LITTLE_ENDIAN = 4, /// A 32-bit unsigned integer, stored in little-endian byte order | |
213 | REG_DWORD_BIG_ENDIAN = 5, /// A 32-bit unsigned integer, stored in big-endian byte order | |
214 | REG_LINK = 6, /// A registry link | |
215 | REG_MULTI_SZ = 7, /// A set of zero-terminated strings | |
216 | REG_RESOURCE_LIST = 8, /// A hardware resource list | |
217 | REG_FULL_RESOURCE_DESCRIPTOR = 9, /// A hardware resource descriptor | |
218 | REG_RESOURCE_REQUIREMENTS_LIST = 10, /// A hardware resource requirements list | |
219 | REG_QWORD = 11, /// A 64-bit unsigned integer | |
220 | REG_QWORD_LITTLE_ENDIAN = 11, /// A 64-bit unsigned integer, stored in little-endian byte order | |
221 | } | |
222 | ||
223 | ||
224 | /* ************* private *************** */ | |
225 | ||
226 | import core.sys.windows.winnt : | |
227 | DELETE , | |
228 | READ_CONTROL , | |
229 | WRITE_DAC , | |
230 | WRITE_OWNER , | |
231 | SYNCHRONIZE , | |
232 | ||
233 | STANDARD_RIGHTS_REQUIRED, | |
234 | ||
235 | STANDARD_RIGHTS_READ , | |
236 | STANDARD_RIGHTS_WRITE , | |
237 | STANDARD_RIGHTS_EXECUTE , | |
238 | ||
239 | STANDARD_RIGHTS_ALL , | |
240 | ||
241 | SPECIFIC_RIGHTS_ALL ; | |
242 | ||
243 | import core.sys.windows.winreg : | |
244 | REG_CREATED_NEW_KEY , | |
245 | REG_OPENED_EXISTING_KEY ; | |
246 | ||
247 | // Returns samDesired but without WoW64 flags if not in WoW64 mode | |
248 | // for compatibility with Windows 2000 | |
249 | private REGSAM compatibleRegsam(in REGSAM samDesired) | |
250 | { | |
251 | return isWow64 ? samDesired : cast(REGSAM)(samDesired & ~REGSAM.KEY_WOW64_RES); | |
252 | } | |
253 | ||
254 | ///Returns true, if we are in WoW64 mode and have WoW64 flags | |
255 | private bool haveWoW64Job(in REGSAM samDesired) | |
256 | { | |
257 | return isWow64 && (samDesired & REGSAM.KEY_WOW64_RES); | |
258 | } | |
259 | ||
260 | private REG_VALUE_TYPE _RVT_from_Endian(Endian endian) | |
261 | { | |
262 | final switch (endian) | |
263 | { | |
264 | case Endian.bigEndian: | |
265 | return REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN; | |
266 | ||
267 | case Endian.littleEndian: | |
268 | return REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN; | |
269 | } | |
270 | } | |
271 | ||
272 | private LONG regCloseKey(in HKEY hkey) | |
273 | in | |
274 | { | |
275 | assert(hkey !is null); | |
276 | } | |
5fee5ec3 | 277 | do |
b4c522fa IB |
278 | { |
279 | /* No need to attempt to close any of the standard hive keys. | |
280 | * Although it's documented that calling RegCloseKey() on any of | |
281 | * these hive keys is ignored, we'd rather not trust the Win32 | |
282 | * API. | |
283 | */ | |
284 | if (cast(uint) hkey & 0x80000000) | |
285 | { | |
286 | switch (cast(uint) hkey) | |
287 | { | |
288 | case HKEY_CLASSES_ROOT: | |
289 | case HKEY_CURRENT_USER: | |
290 | case HKEY_LOCAL_MACHINE: | |
291 | case HKEY_USERS: | |
292 | case HKEY_PERFORMANCE_DATA: | |
293 | case HKEY_PERFORMANCE_TEXT: | |
294 | case HKEY_PERFORMANCE_NLSTEXT: | |
295 | case HKEY_CURRENT_CONFIG: | |
296 | case HKEY_DYN_DATA: | |
297 | return ERROR_SUCCESS; | |
298 | default: | |
299 | /* Do nothing */ | |
300 | break; | |
301 | } | |
302 | } | |
303 | ||
304 | return RegCloseKey(hkey); | |
305 | } | |
306 | ||
307 | private void regFlushKey(in HKEY hkey) | |
308 | in | |
309 | { | |
310 | assert(hkey !is null); | |
311 | } | |
5fee5ec3 | 312 | do |
b4c522fa IB |
313 | { |
314 | immutable res = RegFlushKey(hkey); | |
315 | enforceSucc(res, "Key cannot be flushed"); | |
316 | } | |
317 | ||
318 | private HKEY regCreateKey(in HKEY hkey, in string subKey, in DWORD dwOptions, in REGSAM samDesired, | |
319 | in LPSECURITY_ATTRIBUTES lpsa, out DWORD disposition) | |
320 | in | |
321 | { | |
322 | assert(hkey !is null); | |
323 | assert(subKey !is null); | |
324 | } | |
5fee5ec3 | 325 | do |
b4c522fa IB |
326 | { |
327 | HKEY hkeyResult; | |
328 | enforceSucc(RegCreateKeyExW( | |
329 | hkey, subKey.tempCStringW(), 0, null, dwOptions, | |
330 | compatibleRegsam(samDesired), cast(LPSECURITY_ATTRIBUTES) lpsa, | |
331 | &hkeyResult, &disposition), | |
332 | "Failed to create requested key: \"" ~ subKey ~ "\""); | |
333 | ||
334 | return hkeyResult; | |
335 | } | |
336 | ||
337 | private void regDeleteKey(in HKEY hkey, in string subKey, in REGSAM samDesired) | |
338 | in | |
339 | { | |
340 | assert(hkey !is null); | |
341 | assert(subKey !is null); | |
342 | } | |
5fee5ec3 | 343 | do |
b4c522fa IB |
344 | { |
345 | LONG res; | |
346 | if (haveWoW64Job(samDesired)) | |
347 | { | |
348 | loadAdvapi32(); | |
349 | res = pRegDeleteKeyExW(hkey, subKey.tempCStringW(), samDesired, 0); | |
350 | } | |
351 | else | |
352 | { | |
353 | res = RegDeleteKeyW(hkey, subKey.tempCStringW()); | |
354 | } | |
355 | enforceSucc(res, "Key cannot be deleted: \"" ~ subKey ~ "\""); | |
356 | } | |
357 | ||
358 | private void regDeleteValue(in HKEY hkey, in string valueName) | |
359 | in | |
360 | { | |
361 | assert(hkey !is null); | |
362 | assert(valueName !is null); | |
363 | } | |
5fee5ec3 | 364 | do |
b4c522fa IB |
365 | { |
366 | enforceSucc(RegDeleteValueW(hkey, valueName.tempCStringW()), | |
367 | "Value cannot be deleted: \"" ~ valueName ~ "\""); | |
368 | } | |
369 | ||
370 | private HKEY regDup(HKEY hkey) | |
371 | in | |
372 | { | |
373 | assert(hkey !is null); | |
374 | } | |
5fee5ec3 | 375 | do |
b4c522fa IB |
376 | { |
377 | /* Can't duplicate standard keys, but don't need to, so can just return */ | |
378 | if (cast(uint) hkey & 0x80000000) | |
379 | { | |
380 | switch (cast(uint) hkey) | |
381 | { | |
382 | case HKEY_CLASSES_ROOT: | |
383 | case HKEY_CURRENT_USER: | |
384 | case HKEY_LOCAL_MACHINE: | |
385 | case HKEY_USERS: | |
386 | case HKEY_PERFORMANCE_DATA: | |
387 | case HKEY_PERFORMANCE_TEXT: | |
388 | case HKEY_PERFORMANCE_NLSTEXT: | |
389 | case HKEY_CURRENT_CONFIG: | |
390 | case HKEY_DYN_DATA: | |
391 | return hkey; | |
392 | default: | |
393 | /* Do nothing */ | |
394 | break; | |
395 | } | |
396 | } | |
397 | ||
398 | HKEY hkeyDup; | |
399 | immutable res = RegOpenKeyW(hkey, null, &hkeyDup); | |
400 | ||
401 | debug(winreg) | |
402 | { | |
403 | if (res != ERROR_SUCCESS) | |
404 | { | |
405 | writefln("regDup() failed: 0x%08x 0x%08x %d", hkey, hkeyDup, res); | |
406 | } | |
407 | ||
408 | assert(res == ERROR_SUCCESS); | |
409 | } | |
410 | ||
411 | return (res == ERROR_SUCCESS) ? hkeyDup : null; | |
412 | } | |
413 | ||
414 | private LONG regEnumKeyName(in HKEY hkey, in DWORD index, ref wchar[] name, out DWORD cchName) | |
415 | in | |
416 | { | |
417 | assert(hkey !is null); | |
418 | assert(name !is null); | |
419 | assert(name.length > 0); | |
420 | } | |
421 | out(res) | |
422 | { | |
423 | assert(res != ERROR_MORE_DATA); | |
424 | } | |
5fee5ec3 | 425 | do |
b4c522fa IB |
426 | { |
427 | // The Registry API lies about the lengths of a very few sub-key lengths | |
428 | // so we have to test to see if it whinges about more data, and provide | |
429 | // more if it does. | |
430 | for (;;) | |
431 | { | |
432 | cchName = to!DWORD(name.length); | |
433 | immutable res = RegEnumKeyExW(hkey, index, name.ptr, &cchName, null, null, null, null); | |
434 | if (res != ERROR_MORE_DATA) | |
435 | return res; | |
436 | ||
437 | // Now need to increase the size of the buffer and try again | |
438 | name.length *= 2; | |
439 | } | |
440 | ||
441 | assert(0); | |
442 | } | |
443 | ||
444 | ||
445 | private LONG regEnumValueName(in HKEY hkey, in DWORD dwIndex, ref wchar[] name, out DWORD cchName) | |
446 | in | |
447 | { | |
448 | assert(hkey !is null); | |
449 | } | |
5fee5ec3 | 450 | do |
b4c522fa IB |
451 | { |
452 | for (;;) | |
453 | { | |
454 | cchName = to!DWORD(name.length); | |
455 | immutable res = RegEnumValueW(hkey, dwIndex, name.ptr, &cchName, null, null, null, null); | |
456 | if (res != ERROR_MORE_DATA) | |
457 | return res; | |
458 | ||
459 | name.length *= 2; | |
460 | } | |
461 | ||
462 | assert(0); | |
463 | } | |
464 | ||
465 | private LONG regGetNumSubKeys(in HKEY hkey, out DWORD cSubKeys, out DWORD cchSubKeyMaxLen) | |
466 | in | |
467 | { | |
468 | assert(hkey !is null); | |
469 | } | |
5fee5ec3 | 470 | do |
b4c522fa IB |
471 | { |
472 | return RegQueryInfoKeyW(hkey, null, null, null, &cSubKeys, | |
473 | &cchSubKeyMaxLen, null, null, null, null, null, null); | |
474 | } | |
475 | ||
476 | private LONG regGetNumValues(in HKEY hkey, out DWORD cValues, out DWORD cchValueMaxLen) | |
477 | in | |
478 | { | |
479 | assert(hkey !is null); | |
480 | } | |
5fee5ec3 | 481 | do |
b4c522fa IB |
482 | { |
483 | return RegQueryInfoKeyW(hkey, null, null, null, null, null, null, | |
484 | &cValues, &cchValueMaxLen, null, null, null); | |
485 | } | |
486 | ||
487 | private REG_VALUE_TYPE regGetValueType(in HKEY hkey, in string name) | |
488 | in | |
489 | { | |
490 | assert(hkey !is null); | |
491 | } | |
5fee5ec3 | 492 | do |
b4c522fa IB |
493 | { |
494 | REG_VALUE_TYPE type; | |
495 | enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, null, null), | |
496 | "Value cannot be opened: \"" ~ name ~ "\""); | |
497 | ||
498 | return type; | |
499 | } | |
500 | ||
501 | private HKEY regOpenKey(in HKEY hkey, in string subKey, in REGSAM samDesired) | |
502 | in | |
503 | { | |
504 | assert(hkey !is null); | |
505 | assert(subKey !is null); | |
506 | } | |
5fee5ec3 | 507 | do |
b4c522fa IB |
508 | { |
509 | HKEY hkeyResult; | |
510 | enforceSucc(RegOpenKeyExW(hkey, subKey.tempCStringW(), 0, compatibleRegsam(samDesired), &hkeyResult), | |
511 | "Failed to open requested key: \"" ~ subKey ~ "\""); | |
512 | ||
513 | return hkeyResult; | |
514 | } | |
515 | ||
516 | private void regQueryValue(in HKEY hkey, string name, out string value, REG_VALUE_TYPE reqType) | |
517 | in | |
518 | { | |
519 | assert(hkey !is null); | |
520 | } | |
5fee5ec3 | 521 | do |
b4c522fa IB |
522 | { |
523 | import core.bitop : bswap; | |
524 | ||
525 | REG_VALUE_TYPE type; | |
526 | ||
5fee5ec3 | 527 | // See https://issues.dlang.org/show_bug.cgi?id=961 on this |
b4c522fa IB |
528 | union U |
529 | { | |
530 | uint dw; | |
531 | ulong qw; | |
532 | } | |
533 | U u; | |
534 | void* data = &u.qw; | |
535 | DWORD cbData = u.qw.sizeof; | |
536 | ||
537 | auto keynameTmp = name.tempCStringW(); | |
538 | LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData); | |
539 | if (res == ERROR_MORE_DATA) | |
540 | { | |
541 | data = (new ubyte[cbData]).ptr; | |
542 | res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data, &cbData); | |
543 | } | |
544 | ||
545 | enforceSucc(res, | |
546 | "Cannot read the requested value"); | |
547 | enforce(type == reqType, | |
548 | new RegistryException("Value type has been changed since the value was acquired")); | |
549 | ||
550 | switch (type) | |
551 | { | |
552 | case REG_VALUE_TYPE.REG_SZ: | |
553 | case REG_VALUE_TYPE.REG_EXPAND_SZ: | |
554 | auto wstr = (cast(immutable(wchar)*)data)[0 .. cbData / wchar.sizeof]; | |
555 | assert(wstr.length > 0 && wstr[$-1] == '\0'); | |
556 | if (wstr.length && wstr[$-1] == '\0') | |
557 | wstr.length = wstr.length - 1; | |
558 | assert(wstr.length == 0 || wstr[$-1] != '\0'); | |
559 | value = wstr.to!string; | |
560 | break; | |
561 | ||
562 | case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN: | |
563 | version (LittleEndian) | |
564 | value = to!string(u.dw); | |
565 | else | |
566 | value = to!string(bswap(u.dw)); | |
567 | break; | |
568 | ||
569 | case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: | |
570 | version (LittleEndian) | |
571 | value = to!string(bswap(u.dw)); | |
572 | else | |
573 | value = to!string(u.dw); | |
574 | break; | |
575 | ||
576 | case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN: | |
577 | value = to!string(u.qw); | |
578 | break; | |
579 | ||
580 | case REG_VALUE_TYPE.REG_BINARY: | |
581 | case REG_VALUE_TYPE.REG_MULTI_SZ: | |
582 | default: | |
583 | throw new RegistryException("Cannot read the given value as a string"); | |
584 | } | |
585 | } | |
586 | ||
587 | private void regQueryValue(in HKEY hkey, in string name, out string[] value, REG_VALUE_TYPE reqType) | |
588 | in | |
589 | { | |
590 | assert(hkey !is null); | |
591 | } | |
5fee5ec3 | 592 | do |
b4c522fa IB |
593 | { |
594 | REG_VALUE_TYPE type; | |
595 | ||
596 | auto keynameTmp = name.tempCStringW(); | |
597 | wchar[] data = new wchar[256]; | |
598 | DWORD cbData = to!DWORD(data.length * wchar.sizeof); | |
599 | LONG res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); | |
600 | if (res == ERROR_MORE_DATA) | |
601 | { | |
602 | data.length = cbData / wchar.sizeof; | |
603 | res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); | |
604 | } | |
605 | else if (res == ERROR_SUCCESS) | |
606 | { | |
607 | data.length = cbData / wchar.sizeof; | |
608 | } | |
609 | enforceSucc(res, "Cannot read the requested value"); | |
610 | enforce(type == REG_VALUE_TYPE.REG_MULTI_SZ, | |
611 | new RegistryException("Cannot read the given value as a string")); | |
612 | enforce(type == reqType, | |
613 | new RegistryException("Value type has been changed since the value was acquired")); | |
614 | ||
615 | // Remove last two (or one) null terminator | |
616 | assert(data.length > 0 && data[$-1] == '\0'); | |
617 | data.length = data.length - 1; | |
618 | if (data.length > 0 && data[$-1] == '\0') | |
619 | data.length = data.length - 1; | |
620 | ||
621 | auto list = std.array.split(data[], "\0"); | |
622 | value.length = list.length; | |
623 | foreach (i, ref v; value) | |
624 | { | |
625 | v = list[i].to!string; | |
626 | } | |
627 | } | |
628 | ||
629 | private void regQueryValue(in HKEY hkey, in string name, out uint value, REG_VALUE_TYPE reqType) | |
630 | in | |
631 | { | |
632 | assert(hkey !is null); | |
633 | } | |
5fee5ec3 | 634 | do |
b4c522fa IB |
635 | { |
636 | import core.bitop : bswap; | |
637 | ||
638 | REG_VALUE_TYPE type; | |
639 | ||
640 | DWORD cbData = value.sizeof; | |
641 | enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData), | |
642 | "Cannot read the requested value"); | |
643 | enforce(type == reqType, | |
644 | new RegistryException("Value type has been changed since the value was acquired")); | |
645 | ||
646 | switch (type) | |
647 | { | |
648 | case REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN: | |
649 | version (LittleEndian) | |
650 | static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN); | |
651 | else | |
652 | value = bswap(value); | |
653 | break; | |
654 | ||
655 | case REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN: | |
656 | version (LittleEndian) | |
657 | value = bswap(value); | |
658 | else | |
659 | static assert(REG_VALUE_TYPE.REG_DWORD == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN); | |
660 | break; | |
661 | ||
662 | default: | |
663 | throw new RegistryException("Cannot read the given value as a 32-bit integer"); | |
664 | } | |
665 | } | |
666 | ||
667 | private void regQueryValue(in HKEY hkey, in string name, out ulong value, REG_VALUE_TYPE reqType) | |
668 | in | |
669 | { | |
670 | assert(hkey !is null); | |
671 | } | |
5fee5ec3 | 672 | do |
b4c522fa IB |
673 | { |
674 | REG_VALUE_TYPE type; | |
675 | ||
676 | DWORD cbData = value.sizeof; | |
677 | enforceSucc(RegQueryValueExW(hkey, name.tempCStringW(), null, cast(LPDWORD) &type, &value, &cbData), | |
678 | "Cannot read the requested value"); | |
679 | enforce(type == reqType, | |
680 | new RegistryException("Value type has been changed since the value was acquired")); | |
681 | ||
682 | switch (type) | |
683 | { | |
684 | case REG_VALUE_TYPE.REG_QWORD_LITTLE_ENDIAN: | |
685 | break; | |
686 | ||
687 | default: | |
688 | throw new RegistryException("Cannot read the given value as a 64-bit integer"); | |
689 | } | |
690 | } | |
691 | ||
692 | private void regQueryValue(in HKEY hkey, in string name, out byte[] value, REG_VALUE_TYPE reqType) | |
693 | in | |
694 | { | |
695 | assert(hkey !is null); | |
696 | } | |
5fee5ec3 | 697 | do |
b4c522fa IB |
698 | { |
699 | REG_VALUE_TYPE type; | |
700 | ||
701 | byte[] data = new byte[100]; | |
702 | DWORD cbData = to!DWORD(data.length); | |
703 | LONG res; | |
704 | auto keynameTmp = name.tempCStringW(); | |
705 | res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); | |
706 | if (res == ERROR_MORE_DATA) | |
707 | { | |
708 | data.length = cbData; | |
709 | res = RegQueryValueExW(hkey, keynameTmp, null, cast(LPDWORD) &type, data.ptr, &cbData); | |
710 | } | |
711 | enforceSucc(res, "Cannot read the requested value"); | |
712 | enforce(type == reqType, | |
713 | new RegistryException("Value type has been changed since the value was acquired")); | |
714 | ||
715 | switch (type) | |
716 | { | |
717 | case REG_VALUE_TYPE.REG_BINARY: | |
718 | data.length = cbData; | |
719 | value = data; | |
720 | break; | |
721 | ||
722 | default: | |
723 | throw new RegistryException("Cannot read the given value as a string"); | |
724 | } | |
725 | } | |
726 | ||
727 | private void regSetValue(in HKEY hkey, in string subKey, in REG_VALUE_TYPE type, in LPCVOID lpData, in DWORD cbData) | |
728 | in | |
729 | { | |
730 | assert(hkey !is null); | |
731 | } | |
5fee5ec3 | 732 | do |
b4c522fa IB |
733 | { |
734 | enforceSucc(RegSetValueExW(hkey, subKey.tempCStringW(), 0, type, cast(BYTE*) lpData, cbData), | |
735 | "Value cannot be set: \"" ~ subKey ~ "\""); | |
736 | } | |
737 | ||
738 | private void regProcessNthKey(Key key, scope void delegate(scope LONG delegate(DWORD, out string)) dg) | |
739 | { | |
740 | DWORD cSubKeys; | |
741 | DWORD cchSubKeyMaxLen; | |
742 | ||
743 | immutable res = regGetNumSubKeys(key.m_hkey, cSubKeys, cchSubKeyMaxLen); | |
744 | assert(res == ERROR_SUCCESS); | |
745 | ||
746 | wchar[] sName = new wchar[cchSubKeyMaxLen + 1]; | |
747 | ||
748 | // Capture `key` in the lambda to keep the object alive (and so its HKEY handle open). | |
749 | dg((DWORD index, out string name) | |
750 | { | |
751 | DWORD cchName; | |
752 | immutable res = regEnumKeyName(key.m_hkey, index, sName, cchName); | |
753 | if (res == ERROR_SUCCESS) | |
754 | { | |
755 | name = sName[0 .. cchName].to!string; | |
756 | } | |
757 | return res; | |
758 | }); | |
759 | } | |
760 | ||
761 | private void regProcessNthValue(Key key, scope void delegate(scope LONG delegate(DWORD, out string)) dg) | |
762 | { | |
763 | DWORD cValues; | |
764 | DWORD cchValueMaxLen; | |
765 | ||
766 | immutable res = regGetNumValues(key.m_hkey, cValues, cchValueMaxLen); | |
767 | assert(res == ERROR_SUCCESS); | |
768 | ||
769 | wchar[] sName = new wchar[cchValueMaxLen + 1]; | |
770 | ||
771 | // Capture `key` in the lambda to keep the object alive (and so its HKEY handle open). | |
772 | dg((DWORD index, out string name) | |
773 | { | |
774 | DWORD cchName; | |
775 | immutable res = regEnumValueName(key.m_hkey, index, sName, cchName); | |
776 | if (res == ERROR_SUCCESS) | |
777 | { | |
778 | name = sName[0 .. cchName].to!string; | |
779 | } | |
780 | return res; | |
781 | }); | |
782 | } | |
783 | ||
784 | /* ************* public classes *************** */ | |
785 | ||
786 | /** | |
787 | This class represents a registry key. | |
788 | */ | |
789 | class Key | |
790 | { | |
791 | @safe pure nothrow | |
792 | invariant() | |
793 | { | |
794 | assert(m_hkey !is null); | |
795 | } | |
796 | ||
797 | private: | |
798 | @safe pure nothrow | |
799 | this(HKEY hkey, string name, bool created) | |
800 | in | |
801 | { | |
802 | assert(hkey !is null); | |
803 | } | |
5fee5ec3 | 804 | do |
b4c522fa IB |
805 | { |
806 | m_hkey = hkey; | |
807 | m_name = name; | |
808 | } | |
809 | ||
810 | ~this() | |
811 | { | |
812 | regCloseKey(m_hkey); | |
813 | ||
814 | // Even though this is horried waste-of-cycles programming | |
815 | // we're doing it here so that the | |
816 | m_hkey = null; | |
817 | } | |
818 | ||
819 | public: | |
820 | /// The name of the key | |
821 | @property string name() @safe pure nothrow const | |
822 | { | |
823 | return m_name; | |
824 | } | |
825 | ||
826 | /** | |
827 | The number of sub keys. | |
828 | */ | |
829 | @property size_t keyCount() const | |
830 | { | |
831 | uint cSubKeys; | |
832 | uint cchSubKeyMaxLen; | |
833 | enforceSucc(regGetNumSubKeys(m_hkey, cSubKeys, cchSubKeyMaxLen), | |
834 | "Number of sub-keys cannot be determined"); | |
835 | ||
836 | return cSubKeys; | |
837 | } | |
838 | ||
839 | /** | |
840 | An enumerable sequence of all the sub-keys of this key. | |
841 | */ | |
842 | @property KeySequence keys() @safe pure | |
843 | { | |
844 | return new KeySequence(this); | |
845 | } | |
846 | ||
847 | /** | |
848 | An enumerable sequence of the names of all the sub-keys of this key. | |
849 | */ | |
850 | @property KeyNameSequence keyNames() @safe pure | |
851 | { | |
852 | return new KeyNameSequence(this); | |
853 | } | |
854 | ||
855 | /** | |
856 | The number of values. | |
857 | */ | |
858 | @property size_t valueCount() const | |
859 | { | |
860 | uint cValues; | |
861 | uint cchValueMaxLen; | |
862 | enforceSucc(regGetNumValues(m_hkey, cValues, cchValueMaxLen), | |
863 | "Number of values cannot be determined"); | |
864 | ||
865 | return cValues; | |
866 | } | |
867 | ||
868 | /** | |
869 | An enumerable sequence of all the values of this key. | |
870 | */ | |
871 | @property ValueSequence values() @safe pure | |
872 | { | |
873 | return new ValueSequence(this); | |
874 | } | |
875 | ||
876 | /** | |
877 | An enumerable sequence of the names of all the values of this key. | |
878 | */ | |
879 | @property ValueNameSequence valueNames() @safe pure | |
880 | { | |
881 | return new ValueNameSequence(this); | |
882 | } | |
883 | ||
884 | public: | |
885 | /** | |
886 | Returns the named sub-key of this key. | |
887 | ||
888 | Params: | |
5fee5ec3 | 889 | name = The name of the subkey to create. May not be `null`. |
b4c522fa IB |
890 | Returns: |
891 | The created key. | |
892 | Throws: | |
5fee5ec3 | 893 | `RegistryException` is thrown if the key cannot be created. |
b4c522fa IB |
894 | */ |
895 | Key createKey(string name, REGSAM access = REGSAM.KEY_ALL_ACCESS) | |
896 | { | |
897 | enforce(!name.empty, new RegistryException("Key name is invalid")); | |
898 | ||
899 | DWORD disposition; | |
900 | HKEY hkey = regCreateKey(m_hkey, name, 0, access, null, disposition); | |
901 | assert(hkey !is null); | |
902 | ||
903 | // Potential resource leak here!! | |
904 | // | |
905 | // If the allocation of the memory for Key fails, the HKEY could be | |
906 | // lost. Hence, we catch such a failure by the finally, and release | |
907 | // the HKEY there. If the creation of | |
908 | try | |
909 | { | |
910 | Key key = new Key(hkey, name, disposition == REG_CREATED_NEW_KEY); | |
911 | hkey = null; | |
912 | return key; | |
913 | } | |
914 | finally | |
915 | { | |
916 | if (hkey !is null) | |
917 | { | |
918 | regCloseKey(hkey); | |
919 | } | |
920 | } | |
921 | } | |
922 | ||
923 | /** | |
924 | Returns the named sub-key of this key. | |
925 | ||
926 | Params: | |
927 | name = The name of the subkey to aquire. If name is the empty | |
928 | string, then the called key is duplicated. | |
5fee5ec3 | 929 | access = The desired access; one of the `REGSAM` enumeration. |
b4c522fa IB |
930 | Returns: |
931 | The aquired key. | |
932 | Throws: | |
5fee5ec3 IB |
933 | This function never returns `null`. If a key corresponding to |
934 | the requested name is not found, `RegistryException` is thrown. | |
b4c522fa IB |
935 | */ |
936 | Key getKey(string name, REGSAM access = REGSAM.KEY_READ) | |
937 | { | |
938 | if (name.empty) | |
939 | return new Key(regDup(m_hkey), m_name, false); | |
940 | ||
941 | HKEY hkey = regOpenKey(m_hkey, name, access); | |
942 | assert(hkey !is null); | |
943 | ||
944 | // Potential resource leak here!! | |
945 | // | |
946 | // If the allocation of the memory for Key fails, the HKEY could be | |
947 | // lost. Hence, we catch such a failure by the finally, and release | |
948 | // the HKEY there. If the creation of | |
949 | try | |
950 | { | |
951 | Key key = new Key(hkey, name, false); | |
952 | hkey = null; | |
953 | return key; | |
954 | } | |
955 | finally | |
956 | { | |
957 | if (hkey != null) | |
958 | { | |
959 | regCloseKey(hkey); | |
960 | } | |
961 | } | |
962 | } | |
963 | ||
964 | /** | |
965 | Deletes the named key. | |
966 | ||
967 | Params: | |
5fee5ec3 | 968 | name = The name of the key to delete. May not be `null`. |
b4c522fa IB |
969 | */ |
970 | void deleteKey(string name, REGSAM access = cast(REGSAM) 0) | |
971 | { | |
972 | enforce(!name.empty, new RegistryException("Key name is invalid")); | |
973 | ||
974 | regDeleteKey(m_hkey, name, access); | |
975 | } | |
976 | ||
977 | /** | |
978 | Returns the named value. | |
5fee5ec3 | 979 | If `name` is the empty string, then the default value is returned. |
b4c522fa IB |
980 | |
981 | Returns: | |
5fee5ec3 IB |
982 | This function never returns `null`. If a value corresponding |
983 | to the requested name is not found, `RegistryException` is thrown. | |
b4c522fa IB |
984 | */ |
985 | Value getValue(string name) | |
986 | { | |
987 | return new Value(this, name, regGetValueType(m_hkey, name)); | |
988 | } | |
989 | ||
990 | /** | |
991 | Sets the named value with the given 32-bit unsigned integer value. | |
992 | ||
993 | Params: | |
994 | name = The name of the value to set. If it is the empty string, | |
995 | sets the default value. | |
996 | value = The 32-bit unsigned value to set. | |
997 | Throws: | |
998 | If a value corresponding to the requested name is not found, | |
5fee5ec3 | 999 | `RegistryException` is thrown. |
b4c522fa IB |
1000 | */ |
1001 | void setValue(string name, uint value) | |
1002 | { | |
1003 | setValue(name, value, endian); | |
1004 | } | |
1005 | ||
1006 | /** | |
1007 | Sets the named value with the given 32-bit unsigned integer value, | |
1008 | according to the desired byte-ordering. | |
1009 | ||
1010 | Params: | |
1011 | name = The name of the value to set. If it is the empty string, | |
1012 | sets the default value. | |
1013 | value = The 32-bit unsigned value to set. | |
5fee5ec3 | 1014 | endian = Can be `Endian.BigEndian` or `Endian.LittleEndian`. |
b4c522fa IB |
1015 | Throws: |
1016 | If a value corresponding to the requested name is not found, | |
5fee5ec3 | 1017 | `RegistryException` is thrown. |
b4c522fa IB |
1018 | */ |
1019 | void setValue(string name, uint value, Endian endian) | |
1020 | { | |
1021 | REG_VALUE_TYPE type = _RVT_from_Endian(endian); | |
1022 | ||
1023 | assert(type == REG_VALUE_TYPE.REG_DWORD_BIG_ENDIAN || | |
1024 | type == REG_VALUE_TYPE.REG_DWORD_LITTLE_ENDIAN); | |
1025 | ||
1026 | regSetValue(m_hkey, name, type, &value, value.sizeof); | |
1027 | } | |
1028 | ||
1029 | /** | |
1030 | Sets the named value with the given 64-bit unsigned integer value. | |
1031 | ||
1032 | Params: | |
1033 | name = The name of the value to set. If it is the empty string, | |
1034 | sets the default value. | |
1035 | value = The 64-bit unsigned value to set. | |
1036 | Throws: | |
1037 | If a value corresponding to the requested name is not found, | |
5fee5ec3 | 1038 | `RegistryException` is thrown. |
b4c522fa IB |
1039 | */ |
1040 | void setValue(string name, ulong value) | |
1041 | { | |
1042 | regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_QWORD, &value, value.sizeof); | |
1043 | } | |
1044 | ||
1045 | /** | |
1046 | Sets the named value with the given string value. | |
1047 | ||
1048 | Params: | |
1049 | name = The name of the value to set. If it is the empty string, | |
1050 | sets the default value. | |
1051 | value = The string value to set. | |
1052 | Throws: | |
1053 | If a value corresponding to the requested name is not found, | |
5fee5ec3 | 1054 | `RegistryException` is thrown. |
b4c522fa IB |
1055 | */ |
1056 | void setValue(string name, string value) | |
1057 | { | |
1058 | setValue(name, value, false); | |
1059 | } | |
1060 | ||
1061 | /** | |
1062 | Sets the named value with the given string value. | |
1063 | ||
1064 | Params: | |
1065 | name = The name of the value to set. If it is the empty string, | |
1066 | sets the default value. | |
1067 | value = The string value to set. | |
5fee5ec3 | 1068 | asEXPAND_SZ = If `true`, the value will be stored as an |
b4c522fa IB |
1069 | expandable environment string, otherwise as a normal string. |
1070 | Throws: | |
1071 | If a value corresponding to the requested name is not found, | |
5fee5ec3 | 1072 | `RegistryException` is thrown. |
b4c522fa IB |
1073 | */ |
1074 | void setValue(string name, string value, bool asEXPAND_SZ) | |
1075 | { | |
1076 | auto pszTmp = value.tempCStringW(); | |
1077 | const(void)* data = pszTmp; | |
1078 | DWORD len = to!DWORD(lstrlenW(pszTmp) * wchar.sizeof); | |
1079 | ||
1080 | regSetValue(m_hkey, name, | |
1081 | asEXPAND_SZ ? REG_VALUE_TYPE.REG_EXPAND_SZ | |
1082 | : REG_VALUE_TYPE.REG_SZ, | |
1083 | data, len); | |
1084 | } | |
1085 | ||
1086 | /** | |
1087 | Sets the named value with the given multiple-strings value. | |
1088 | ||
1089 | Params: | |
1090 | name = The name of the value to set. If it is the empty string, | |
1091 | sets the default value. | |
1092 | value = The multiple-strings value to set. | |
1093 | Throws: | |
1094 | If a value corresponding to the requested name is not found, | |
5fee5ec3 | 1095 | `RegistryException` is thrown. |
b4c522fa IB |
1096 | */ |
1097 | void setValue(string name, string[] value) | |
1098 | { | |
1099 | wstring[] data = new wstring[value.length+1]; | |
1100 | foreach (i, ref s; data[0..$-1]) | |
1101 | { | |
1102 | s = value[i].to!wstring; | |
1103 | } | |
1104 | data[$-1] = "\0"; | |
1105 | auto ws = std.array.join(data, "\0"w); | |
1106 | ||
1107 | regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_MULTI_SZ, ws.ptr, to!uint(ws.length * wchar.sizeof)); | |
1108 | } | |
1109 | ||
1110 | /** | |
1111 | Sets the named value with the given binary value. | |
1112 | ||
1113 | Params: | |
1114 | name = The name of the value to set. If it is the empty string, | |
1115 | sets the default value. | |
1116 | value = The binary value to set. | |
1117 | Throws: | |
1118 | If a value corresponding to the requested name is not found, | |
5fee5ec3 | 1119 | `RegistryException` is thrown. |
b4c522fa IB |
1120 | */ |
1121 | void setValue(string name, byte[] value) | |
1122 | { | |
1123 | regSetValue(m_hkey, name, REG_VALUE_TYPE.REG_BINARY, value.ptr, to!DWORD(value.length)); | |
1124 | } | |
1125 | ||
1126 | /** | |
1127 | Deletes the named value. | |
1128 | ||
1129 | Params: | |
5fee5ec3 | 1130 | name = The name of the value to delete. May not be `null`. |
b4c522fa IB |
1131 | Throws: |
1132 | If a value of the requested name is not found, | |
5fee5ec3 | 1133 | `RegistryException` is thrown. |
b4c522fa IB |
1134 | */ |
1135 | void deleteValue(string name) | |
1136 | { | |
1137 | regDeleteValue(m_hkey, name); | |
1138 | } | |
1139 | ||
1140 | /** | |
1141 | Flushes any changes to the key to disk. | |
1142 | */ | |
1143 | void flush() | |
1144 | { | |
1145 | regFlushKey(m_hkey); | |
1146 | } | |
1147 | ||
1148 | private: | |
1149 | HKEY m_hkey; | |
1150 | string m_name; | |
1151 | } | |
1152 | ||
1153 | /** | |
1154 | This class represents a value of a registry key. | |
1155 | */ | |
1156 | class Value | |
1157 | { | |
1158 | @safe pure nothrow | |
1159 | invariant() | |
1160 | { | |
1161 | assert(m_key !is null); | |
1162 | } | |
1163 | ||
1164 | private: | |
1165 | @safe pure nothrow | |
1166 | this(Key key, string name, REG_VALUE_TYPE type) | |
1167 | in | |
1168 | { | |
1169 | assert(null !is key); | |
1170 | } | |
5fee5ec3 | 1171 | do |
b4c522fa IB |
1172 | { |
1173 | m_key = key; | |
1174 | m_type = type; | |
1175 | m_name = name; | |
1176 | } | |
1177 | ||
1178 | public: | |
1179 | /** | |
1180 | The name of the value. | |
1181 | If the value represents a default value of a key, which has no name, | |
1182 | the returned string will be of zero length. | |
1183 | */ | |
1184 | @property string name() @safe pure nothrow const | |
1185 | { | |
1186 | return m_name; | |
1187 | } | |
1188 | ||
1189 | /** | |
1190 | The type of value. | |
1191 | */ | |
1192 | @property REG_VALUE_TYPE type() @safe pure nothrow const | |
1193 | { | |
1194 | return m_type; | |
1195 | } | |
1196 | ||
1197 | /** | |
1198 | Obtains the current value of the value as a string. | |
1199 | If the value's type is REG_EXPAND_SZ the returned value is <b>not</b> | |
5fee5ec3 | 1200 | expanded; `value_EXPAND_SZ` should be called |
b4c522fa IB |
1201 | |
1202 | Returns: | |
1203 | The contents of the value. | |
1204 | Throws: | |
5fee5ec3 | 1205 | `RegistryException` if the type of the value is not REG_SZ, |
b4c522fa IB |
1206 | REG_EXPAND_SZ, REG_DWORD, or REG_QWORD. |
1207 | */ | |
1208 | @property string value_SZ() const | |
1209 | { | |
1210 | string value; | |
1211 | ||
1212 | regQueryValue(m_key.m_hkey, m_name, value, m_type); | |
1213 | ||
1214 | return value; | |
1215 | } | |
1216 | ||
1217 | /** | |
1218 | Obtains the current value as a string, within which any environment | |
1219 | variables have undergone expansion. | |
5fee5ec3 | 1220 | This function works with the same value-types as `value_SZ`. |
b4c522fa IB |
1221 | |
1222 | Returns: | |
1223 | The contents of the value. | |
1224 | */ | |
1225 | @property string value_EXPAND_SZ() const | |
1226 | { | |
1227 | string value = value_SZ; | |
1228 | ||
1229 | // ExpandEnvironemntStrings(): | |
1230 | // http://msdn2.microsoft.com/en-us/library/ms724265.aspx | |
1231 | const srcTmp = value.tempCStringW(); | |
1232 | DWORD cchRequired = ExpandEnvironmentStringsW(srcTmp, null, 0); | |
1233 | wchar[] newValue = new wchar[cchRequired]; | |
1234 | ||
1235 | immutable DWORD count = enforce!Win32Exception( | |
1236 | ExpandEnvironmentStringsW(srcTmp, newValue.ptr, to!DWORD(newValue.length)), | |
1237 | "Failed to expand environment variables"); | |
1238 | ||
1239 | return newValue[0 .. count-1].to!string; // remove trailing 0 | |
1240 | } | |
1241 | ||
1242 | /** | |
1243 | Obtains the current value as an array of strings. | |
1244 | ||
1245 | Returns: | |
1246 | The contents of the value. | |
1247 | Throws: | |
5fee5ec3 | 1248 | `RegistryException` if the type of the value is not REG_MULTI_SZ. |
b4c522fa IB |
1249 | */ |
1250 | @property string[] value_MULTI_SZ() const | |
1251 | { | |
1252 | string[] value; | |
1253 | ||
1254 | regQueryValue(m_key.m_hkey, m_name, value, m_type); | |
1255 | ||
1256 | return value; | |
1257 | } | |
1258 | ||
1259 | /** | |
1260 | Obtains the current value as a 32-bit unsigned integer, ordered | |
1261 | correctly according to the current architecture. | |
1262 | ||
1263 | Returns: | |
1264 | The contents of the value. | |
1265 | Throws: | |
5fee5ec3 | 1266 | `RegistryException` is thrown for all types other than |
b4c522fa IB |
1267 | REG_DWORD, REG_DWORD_LITTLE_ENDIAN and REG_DWORD_BIG_ENDIAN. |
1268 | */ | |
1269 | @property uint value_DWORD() const | |
1270 | { | |
1271 | uint value; | |
1272 | ||
1273 | regQueryValue(m_key.m_hkey, m_name, value, m_type); | |
1274 | ||
1275 | return value; | |
1276 | } | |
1277 | ||
1278 | /** | |
1279 | Obtains the value as a 64-bit unsigned integer, ordered correctly | |
1280 | according to the current architecture. | |
1281 | ||
1282 | Returns: | |
1283 | The contents of the value. | |
1284 | Throws: | |
5fee5ec3 | 1285 | `RegistryException` if the type of the value is not REG_QWORD. |
b4c522fa IB |
1286 | */ |
1287 | @property ulong value_QWORD() const | |
1288 | { | |
1289 | ulong value; | |
1290 | ||
1291 | regQueryValue(m_key.m_hkey, m_name, value, m_type); | |
1292 | ||
1293 | return value; | |
1294 | } | |
1295 | ||
1296 | /** | |
1297 | Obtains the value as a binary blob. | |
1298 | ||
1299 | Returns: | |
1300 | The contents of the value. | |
1301 | Throws: | |
5fee5ec3 | 1302 | `RegistryException` if the type of the value is not REG_BINARY. |
b4c522fa IB |
1303 | */ |
1304 | @property byte[] value_BINARY() const | |
1305 | { | |
1306 | byte[] value; | |
1307 | ||
1308 | regQueryValue(m_key.m_hkey, m_name, value, m_type); | |
1309 | ||
1310 | return value; | |
1311 | } | |
1312 | ||
1313 | private: | |
1314 | Key m_key; | |
1315 | REG_VALUE_TYPE m_type; | |
1316 | string m_name; | |
1317 | } | |
1318 | ||
1319 | /** | |
1320 | Represents the local system registry. | |
1321 | */ | |
1322 | final class Registry | |
1323 | { | |
1324 | private: | |
5fee5ec3 | 1325 | @disable this(); |
b4c522fa IB |
1326 | |
1327 | public: | |
1328 | /// Returns the root key for the HKEY_CLASSES_ROOT hive | |
1329 | static @property Key classesRoot() { return new Key(HKEY_CLASSES_ROOT, "HKEY_CLASSES_ROOT", false); } | |
1330 | /// Returns the root key for the HKEY_CURRENT_USER hive | |
1331 | static @property Key currentUser() { return new Key(HKEY_CURRENT_USER, "HKEY_CURRENT_USER", false); } | |
1332 | /// Returns the root key for the HKEY_LOCAL_MACHINE hive | |
1333 | static @property Key localMachine() { return new Key(HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE", false); } | |
1334 | /// Returns the root key for the HKEY_USERS hive | |
1335 | static @property Key users() { return new Key(HKEY_USERS, "HKEY_USERS", false); } | |
1336 | /// Returns the root key for the HKEY_PERFORMANCE_DATA hive | |
1337 | static @property Key performanceData() { return new Key(HKEY_PERFORMANCE_DATA, "HKEY_PERFORMANCE_DATA", false); } | |
1338 | /// Returns the root key for the HKEY_CURRENT_CONFIG hive | |
1339 | static @property Key currentConfig() { return new Key(HKEY_CURRENT_CONFIG, "HKEY_CURRENT_CONFIG", false); } | |
1340 | /// Returns the root key for the HKEY_DYN_DATA hive | |
1341 | static @property Key dynData() { return new Key(HKEY_DYN_DATA, "HKEY_DYN_DATA", false); } | |
1342 | } | |
1343 | ||
1344 | /** | |
1345 | An enumerable sequence representing the names of the sub-keys of a registry Key. | |
1346 | ||
1347 | Example: | |
1348 | ---- | |
1349 | Key key = ... | |
1350 | foreach (string subkeyName; key.keyNames) | |
1351 | { | |
1352 | // using subkeyName | |
1353 | } | |
1354 | ---- | |
1355 | */ | |
1356 | class KeyNameSequence | |
1357 | { | |
1358 | @safe pure nothrow | |
1359 | invariant() | |
1360 | { | |
1361 | assert(m_key !is null); | |
1362 | } | |
1363 | ||
1364 | private: | |
1365 | @safe pure nothrow | |
1366 | this(Key key) | |
1367 | { | |
1368 | m_key = key; | |
1369 | } | |
1370 | ||
1371 | public: | |
1372 | /** | |
1373 | The number of keys. | |
1374 | */ | |
1375 | @property size_t count() const | |
1376 | { | |
1377 | return m_key.keyCount; | |
1378 | } | |
1379 | ||
1380 | /** | |
1381 | The name of the key at the given index. | |
1382 | ||
1383 | Params: | |
1384 | index = The 0-based index of the key to retrieve. | |
1385 | Returns: | |
1386 | The name of the key corresponding to the given index. | |
1387 | Throws: | |
1388 | RegistryException if no corresponding key is retrieved. | |
1389 | */ | |
1390 | string getKeyName(size_t index) | |
1391 | { | |
1392 | string name; | |
1393 | regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1394 | { | |
1395 | enforceSucc(getName(to!DWORD(index), name), "Invalid key"); | |
1396 | }); | |
1397 | return name; | |
1398 | } | |
1399 | ||
1400 | /** | |
1401 | The name of the key at the given index. | |
1402 | ||
1403 | Params: | |
1404 | index = The 0-based index of the key to retrieve. | |
1405 | Returns: | |
1406 | The name of the key corresponding to the given index. | |
1407 | Throws: | |
5fee5ec3 | 1408 | `RegistryException` if no corresponding key is retrieved. |
b4c522fa IB |
1409 | */ |
1410 | string opIndex(size_t index) | |
1411 | { | |
1412 | return getKeyName(index); | |
1413 | } | |
1414 | ||
1415 | public: | |
1416 | /// | |
1417 | int opApply(scope int delegate(ref string name) dg) | |
1418 | { | |
1419 | int result; | |
1420 | regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1421 | { | |
1422 | for (DWORD index = 0; !result; ++index) | |
1423 | { | |
1424 | string name; | |
1425 | immutable res = getName(index, name); | |
1426 | if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete | |
1427 | break; | |
1428 | enforceSucc(res, "Key name enumeration incomplete"); | |
1429 | ||
1430 | result = dg(name); | |
1431 | } | |
1432 | }); | |
1433 | return result; | |
1434 | } | |
1435 | ||
1436 | private: | |
1437 | Key m_key; | |
1438 | } | |
1439 | ||
1440 | ||
1441 | /** | |
1442 | An enumerable sequence representing the sub-keys of a registry Key. | |
1443 | ||
1444 | Example: | |
1445 | ---- | |
1446 | Key key = ... | |
1447 | foreach (Key subkey; key.keys) | |
1448 | { | |
1449 | // using subkey | |
1450 | } | |
1451 | ---- | |
1452 | */ | |
1453 | class KeySequence | |
1454 | { | |
1455 | @safe pure nothrow | |
1456 | invariant() | |
1457 | { | |
1458 | assert(m_key !is null); | |
1459 | } | |
1460 | ||
1461 | private: | |
1462 | @safe pure nothrow | |
1463 | this(Key key) | |
1464 | { | |
1465 | m_key = key; | |
1466 | } | |
1467 | ||
1468 | public: | |
1469 | /** | |
1470 | The number of keys. | |
1471 | */ | |
1472 | @property size_t count() const | |
1473 | { | |
1474 | return m_key.keyCount; | |
1475 | } | |
1476 | ||
1477 | /** | |
1478 | The key at the given index. | |
1479 | ||
1480 | Params: | |
1481 | index = The 0-based index of the key to retrieve. | |
1482 | Returns: | |
1483 | The key corresponding to the given index. | |
1484 | Throws: | |
5fee5ec3 | 1485 | `RegistryException` if no corresponding key is retrieved. |
b4c522fa IB |
1486 | */ |
1487 | Key getKey(size_t index) | |
1488 | { | |
1489 | string name; | |
1490 | regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1491 | { | |
1492 | enforceSucc(getName(to!DWORD(index), name), "Invalid key"); | |
1493 | }); | |
1494 | return m_key.getKey(name); | |
1495 | } | |
1496 | ||
1497 | /** | |
1498 | The key at the given index. | |
1499 | ||
1500 | Params: | |
1501 | index = The 0-based index of the key to retrieve. | |
1502 | Returns: | |
1503 | The key corresponding to the given index. | |
1504 | Throws: | |
5fee5ec3 | 1505 | `RegistryException` if no corresponding key is retrieved. |
b4c522fa IB |
1506 | */ |
1507 | Key opIndex(size_t index) | |
1508 | { | |
1509 | return getKey(index); | |
1510 | } | |
1511 | ||
1512 | public: | |
1513 | /// | |
1514 | int opApply(scope int delegate(ref Key key) dg) | |
1515 | { | |
1516 | int result = 0; | |
1517 | regProcessNthKey(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1518 | { | |
1519 | for (DWORD index = 0; !result; ++index) | |
1520 | { | |
1521 | string name; | |
1522 | immutable res = getName(index, name); | |
1523 | if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete | |
1524 | break; | |
1525 | enforceSucc(res, "Key enumeration incomplete"); | |
1526 | ||
1527 | try | |
1528 | { | |
1529 | Key key = m_key.getKey(name); | |
1530 | result = dg(key); | |
1531 | } | |
1532 | catch (RegistryException e) | |
1533 | { | |
1534 | // Skip inaccessible keys; they are | |
1535 | // accessible via the KeyNameSequence | |
1536 | if (e.error == ERROR_ACCESS_DENIED) | |
1537 | continue; | |
1538 | ||
1539 | throw e; | |
1540 | } | |
1541 | } | |
1542 | }); | |
1543 | return result; | |
1544 | } | |
1545 | ||
1546 | private: | |
1547 | Key m_key; | |
1548 | } | |
1549 | ||
1550 | /** | |
1551 | An enumerable sequence representing the names of the values of a registry Key. | |
1552 | ||
1553 | Example: | |
1554 | ---- | |
1555 | Key key = ... | |
1556 | foreach (string valueName; key.valueNames) | |
1557 | { | |
1558 | // using valueName | |
1559 | } | |
1560 | ---- | |
1561 | */ | |
1562 | class ValueNameSequence | |
1563 | { | |
1564 | @safe pure nothrow | |
1565 | invariant() | |
1566 | { | |
1567 | assert(m_key !is null); | |
1568 | } | |
1569 | ||
1570 | private: | |
1571 | @safe pure nothrow | |
1572 | this(Key key) | |
1573 | { | |
1574 | m_key = key; | |
1575 | } | |
1576 | ||
1577 | public: | |
1578 | /** | |
1579 | The number of values. | |
1580 | */ | |
1581 | @property size_t count() const | |
1582 | { | |
1583 | return m_key.valueCount; | |
1584 | } | |
1585 | ||
1586 | /** | |
1587 | The name of the value at the given index. | |
1588 | ||
1589 | Params: | |
1590 | index = The 0-based index of the value to retrieve. | |
1591 | Returns: | |
1592 | The name of the value corresponding to the given index. | |
1593 | Throws: | |
5fee5ec3 | 1594 | `RegistryException` if no corresponding value is retrieved. |
b4c522fa IB |
1595 | */ |
1596 | string getValueName(size_t index) | |
1597 | { | |
1598 | string name; | |
1599 | regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1600 | { | |
1601 | enforceSucc(getName(to!DWORD(index), name), "Invalid value"); | |
1602 | }); | |
1603 | return name; | |
1604 | } | |
1605 | ||
1606 | /** | |
1607 | The name of the value at the given index. | |
1608 | ||
1609 | Params: | |
1610 | index = The 0-based index of the value to retrieve. | |
1611 | Returns: | |
1612 | The name of the value corresponding to the given index. | |
1613 | Throws: | |
5fee5ec3 | 1614 | `RegistryException` if no corresponding value is retrieved. |
b4c522fa IB |
1615 | */ |
1616 | string opIndex(size_t index) | |
1617 | { | |
1618 | return getValueName(index); | |
1619 | } | |
1620 | ||
1621 | public: | |
1622 | /// | |
1623 | int opApply(scope int delegate(ref string name) dg) | |
1624 | { | |
1625 | int result = 0; | |
1626 | regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1627 | { | |
1628 | for (DWORD index = 0; !result; ++index) | |
1629 | { | |
1630 | string name; | |
1631 | immutable res = getName(index, name); | |
1632 | if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete | |
1633 | break; | |
1634 | enforceSucc(res, "Value name enumeration incomplete"); | |
1635 | ||
1636 | result = dg(name); | |
1637 | } | |
1638 | }); | |
1639 | return result; | |
1640 | } | |
1641 | ||
1642 | private: | |
1643 | Key m_key; | |
1644 | } | |
1645 | ||
1646 | /** | |
1647 | An enumerable sequence representing the values of a registry Key. | |
1648 | ||
1649 | Example: | |
1650 | ---- | |
1651 | Key key = ... | |
1652 | foreach (Value value; key.values) | |
1653 | { | |
1654 | // using value | |
1655 | } | |
1656 | ---- | |
1657 | */ | |
1658 | class ValueSequence | |
1659 | { | |
1660 | @safe pure nothrow | |
1661 | invariant() | |
1662 | { | |
1663 | assert(m_key !is null); | |
1664 | } | |
1665 | ||
1666 | private: | |
1667 | @safe pure nothrow | |
1668 | this(Key key) | |
1669 | { | |
1670 | m_key = key; | |
1671 | } | |
1672 | ||
1673 | public: | |
1674 | /// The number of values | |
1675 | @property size_t count() const | |
1676 | { | |
1677 | return m_key.valueCount; | |
1678 | } | |
1679 | ||
1680 | /** | |
5fee5ec3 | 1681 | The value at the given `index`. |
b4c522fa IB |
1682 | |
1683 | Params: | |
1684 | index = The 0-based index of the value to retrieve | |
1685 | Returns: | |
1686 | The value corresponding to the given index. | |
1687 | Throws: | |
5fee5ec3 | 1688 | `RegistryException` if no corresponding value is retrieved |
b4c522fa IB |
1689 | */ |
1690 | Value getValue(size_t index) | |
1691 | { | |
1692 | string name; | |
1693 | regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1694 | { | |
1695 | enforceSucc(getName(to!DWORD(index), name), "Invalid value"); | |
1696 | }); | |
1697 | return m_key.getValue(name); | |
1698 | } | |
1699 | ||
1700 | /** | |
5fee5ec3 | 1701 | The value at the given `index`. |
b4c522fa IB |
1702 | |
1703 | Params: | |
1704 | index = The 0-based index of the value to retrieve. | |
1705 | Returns: | |
1706 | The value corresponding to the given index. | |
1707 | Throws: | |
5fee5ec3 | 1708 | `RegistryException` if no corresponding value is retrieved. |
b4c522fa IB |
1709 | */ |
1710 | Value opIndex(size_t index) | |
1711 | { | |
1712 | return getValue(index); | |
1713 | } | |
1714 | ||
1715 | public: | |
1716 | /// | |
1717 | int opApply(scope int delegate(ref Value value) dg) | |
1718 | { | |
1719 | int result = 0; | |
1720 | regProcessNthValue(m_key, (scope LONG delegate(DWORD, out string) getName) | |
1721 | { | |
1722 | for (DWORD index = 0; !result; ++index) | |
1723 | { | |
1724 | string name; | |
1725 | immutable res = getName(index, name); | |
1726 | if (res == ERROR_NO_MORE_ITEMS) // Enumeration complete | |
1727 | break; | |
1728 | enforceSucc(res, "Value enumeration incomplete"); | |
1729 | ||
1730 | Value value = m_key.getValue(name); | |
1731 | result = dg(value); | |
1732 | } | |
1733 | }); | |
1734 | return result; | |
1735 | } | |
1736 | ||
1737 | private: | |
1738 | Key m_key; | |
1739 | } | |
1740 | ||
1741 | ||
1742 | @system unittest | |
1743 | { | |
1744 | debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded."); | |
1745 | debug(winreg) writefln("std.windows.registry.unittest read"); | |
1746 | ||
1747 | /+ | |
1748 | // Mask for test speed up | |
1749 | ||
1750 | Key HKCR = Registry.classesRoot; | |
1751 | Key CLSID = HKCR.getKey("CLSID"); | |
1752 | ||
1753 | foreach (Key key; CLSID.keys) | |
1754 | { | |
1755 | foreach (Value val; key.values) | |
1756 | { | |
1757 | } | |
1758 | } | |
1759 | +/ | |
1760 | Key HKCU = Registry.currentUser; | |
1761 | assert(HKCU); | |
1762 | ||
1763 | // Enumerate all subkeys of key Software | |
1764 | Key softwareKey = HKCU.getKey("Software"); | |
1765 | assert(softwareKey); | |
1766 | foreach (Key key; softwareKey.keys) | |
1767 | { | |
1768 | //writefln("Key %s", key.name); | |
1769 | foreach (Value val; key.values) | |
1770 | { | |
1771 | } | |
1772 | } | |
1773 | } | |
1774 | ||
1775 | @system unittest | |
1776 | { | |
1777 | debug(winreg) scope(success) writeln("unittest @", __FILE__, ":", __LINE__, " succeeded."); | |
1778 | debug(winreg) writefln("std.windows.registry.unittest write"); | |
1779 | ||
1780 | // Warning: This unit test writes to the registry. | |
1781 | // The test can fail if you don't have sufficient rights | |
1782 | ||
1783 | Key HKCU = Registry.currentUser; | |
1784 | assert(HKCU); | |
1785 | ||
1786 | // Create a new key | |
1787 | string unittestKeyName = "Temporary key for a D UnitTest which can be deleted afterwards"; | |
1788 | Key unittestKey = HKCU.createKey(unittestKeyName); | |
1789 | assert(unittestKey); | |
1790 | Key cityKey = unittestKey.createKey( | |
1791 | "CityCollection using foreign names with umlauts and accents: " | |
1792 | ~"\u00f6\u00e4\u00fc\u00d6\u00c4\u00dc\u00e0\u00e1\u00e2\u00df" | |
1793 | ); | |
1794 | cityKey.setValue("K\u00f6ln", "Germany"); // Cologne | |
1795 | cityKey.setValue("\u041c\u0438\u043d\u0441\u043a", "Belarus"); // Minsk | |
1796 | cityKey.setValue("\u5317\u4eac", "China"); // Bejing | |
1797 | bool foundCologne, foundMinsk, foundBeijing; | |
1798 | foreach (Value v; cityKey.values) | |
1799 | { | |
1800 | auto vname = v.name; | |
1801 | auto vvalue_SZ = v.value_SZ; | |
1802 | if (v.name == "K\u00f6ln") | |
1803 | { | |
1804 | foundCologne = true; | |
1805 | assert(v.value_SZ == "Germany"); | |
1806 | } | |
1807 | if (v.name == "\u041c\u0438\u043d\u0441\u043a") | |
1808 | { | |
1809 | foundMinsk = true; | |
1810 | assert(v.value_SZ == "Belarus"); | |
1811 | } | |
1812 | if (v.name == "\u5317\u4eac") | |
1813 | { | |
1814 | foundBeijing = true; | |
1815 | assert(v.value_SZ == "China"); | |
1816 | } | |
1817 | } | |
1818 | assert(foundCologne); | |
1819 | assert(foundMinsk); | |
1820 | assert(foundBeijing); | |
1821 | ||
1822 | Key stateKey = unittestKey.createKey("StateCollection"); | |
1823 | stateKey.setValue("Germany", ["D\u00fcsseldorf", "K\u00f6ln", "Hamburg"]); | |
1824 | Value v = stateKey.getValue("Germany"); | |
1825 | string[] actual = v.value_MULTI_SZ; | |
1826 | assert(actual.length == 3); | |
1827 | assert(actual[0] == "D\u00fcsseldorf"); | |
1828 | assert(actual[1] == "K\u00f6ln"); | |
1829 | assert(actual[2] == "Hamburg"); | |
1830 | ||
1831 | Key numberKey = unittestKey.createKey("Number"); | |
1832 | numberKey.setValue("One", 1); | |
1833 | Value one = numberKey.getValue("One"); | |
1834 | assert(one.value_SZ == "1"); | |
1835 | assert(one.value_DWORD == 1); | |
1836 | ||
1837 | unittestKey.deleteKey(numberKey.name); | |
1838 | unittestKey.deleteKey(stateKey.name); | |
1839 | unittestKey.deleteKey(cityKey.name); | |
1840 | HKCU.deleteKey(unittestKeyName); | |
1841 | ||
1842 | auto e = collectException!RegistryException(HKCU.getKey("cDhmxsX9K23a8Uf869uB")); | |
1843 | assert(e.msg.endsWith(" (error 2)")); | |
1844 | } | |
1845 | ||
1846 | @system unittest | |
1847 | { | |
1848 | Key HKCU = Registry.currentUser; | |
1849 | assert(HKCU); | |
1850 | ||
1851 | Key key = HKCU.getKey("Control Panel"); | |
1852 | assert(key); | |
1853 | assert(key.keyCount >= 2); | |
1854 | ||
1855 | // Make sure `key` isn't garbage-collected while iterating over it. | |
1856 | // Trigger a collection in the first iteration and check whether we | |
1857 | // make it successfully to the second iteration. | |
1858 | int i = 0; | |
1859 | foreach (name; key.keyNames) | |
1860 | { | |
1861 | if (i++ > 0) | |
1862 | break; | |
1863 | ||
1864 | import core.memory; | |
1865 | GC.collect(); | |
1866 | } | |
1867 | assert(i == 2); | |
1868 | } |