root/trunk/tango/scrapple/sys/win32/Registry.d

Revision 39, 26.2 kB (checked in by flithm, 11 months ago)

Fixes for Tango 0.99.4

Line 
1 /*
2  * Copyright (c) 2007 John Chapman
3  *
4  * Permission is hereby granted, free of charge, to any person
5  * obtaining a copy of this software and associated documentation
6  * files (the "Software"), to deal in the Software without
7  * restriction, including without limitation the rights to use,
8  * copy, modify, merge, publish, distribute, sublicense, and/or sell
9  * copies of the Software, and to permit persons to whom the
10  * Software is furnished to do so, subject to the following
11  * conditions:
12  *
13  * The above copyright notice and this permission notice shall be
14  * included in all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23  * OTHER DEALINGS IN THE SOFTWARE.
24  */
25
26 /**
27  * Provides classes for working with the Windows _registry.
28  * modified by yidabu, 20070922
29    under DMD 1.021, tango 0.99.1 RC4, WindosXP SP2
30    example:
31   
32     import tango.io.Stdout;
33     import Registry;
34
35     void main()
36     {
37         char[] result;
38         scope httpkey = RegistryKey.classesRoot.openSubKey(r"HTTP\shell\open\command");
39         if(httpkey !is null)
40         {
41             result = httpkey.getValue!(string)(null);
42             Stdout(result).newline;
43             //"C:\Program Files\Internet Explorer\iexplore.exe" -nohome
44         }
45         else
46             Stdout("failed").newline;
47         
48     }   
49  
50  
51  */
52 //module juno.utils.registry;
53 module Registry;
54
55 /*
56 private import juno.base.core,
57   juno.base.string,
58   juno.base.environment,
59   juno.base.numeric,
60   juno.base.native,
61   juno.io.core;
62 */
63 private import tango.core.Exception;
64 private import tango.stdc.string : wcslen;
65 private import tango.text.convert.Utf : toString16, toUTF8 = toString;
66
67 // Grabbed from juno.base.native, use tango.sys.win32.UserGdi have some problem here
68 typedef void* Handle = null;
69 extern(Windows)
70 {
71     extern final Handle INVALID_HANDLE_VALUE; // Already defined by Phobos
72     struct OVERLAPPED {
73       uint Internal;
74       uint InternalHigh;
75       union {
76         struct {
77           uint Offset;
78           uint OffsetHigh;
79         }
80         void* Pointer;
81       }
82       Handle hEvent;
83     }
84     struct FILETIME {
85       uint dwLowDateTime;
86       uint dwHighDateTime;
87     }
88     struct SECURITY_ATTRIBUTES {
89       uint nLength;
90       void* lpSecurityDescriptor;
91       int bInheritHandle;
92     }
93     enum : uint {
94       REG_NONE                        = 0,
95       REG_SZ                          = 1,
96       REG_EXPAND_SZ                   = 2,
97       REG_BINARY                      = 3,
98       REG_DWORD                       = 4,
99       REG_DWORD_LITTLE_ENDIAN         = 4,
100       REG_DWORD_BIG_ENDIAN            = 5,
101       REG_LINK                        = 6,
102       REG_MULTI_SZ                    = 7,
103       REG_RESOURCE_LIST               = 8,
104       REG_FULL_RESOURCE_DESCRIPTOR    = 9,
105       REG_RESOURCE_REQUIREMENTS_LIST  = 10,
106       REG_QWORD                       = 11,
107       REG_QWORD_LITTLE_ENDIAN         = 11
108     }
109     enum : uint {
110       DELETE                          = 0x00010000,
111       READ_CONTROL                    = 0x00020000,
112       WRITE_DAC                       = 0x00040000,
113       WRITE_OWNER                     = 0x00080000,
114       SYNCHRONIZE                     = 0x00100000,
115       STANDARD_RIGHTS_REQUIRED        = 0x000F0000,
116       STANDARD_RIGHTS_READ            = READ_CONTROL,
117       STANDARD_RIGHTS_WRITE           = READ_CONTROL,
118       STANDARD_RIGHTS_EXECUTE         = READ_CONTROL,
119       STANDARD_RIGHTS_ALL             = 0x001F0000,
120       SPECIFIC_RIGHTS_ALL             = 0x0000FFFF
121     }
122     enum {
123       ERROR_SUCCESS = 0,
124       ERROR_INVALID_FUNCTION = 1,
125       ERROR_FILE_NOT_FOUND = 2,
126       ERROR_PATH_NOT_FOUND = 3,
127       ERROR_TOO_MANY_OPEN_FILES = 4,
128       ERROR_ACCESS_DENIED = 5,
129       ERROR_INVALID_HANDLE = 6,
130       ERROR_CLASS_ALREADY_EXISTS = 1410
131     }
132
133     enum : uint {
134       KEY_QUERY_VALUE        = 0x0001,
135       KEY_SET_VALUE          = 0x0002,
136       KEY_CREATE_SUB_KEY     = 0x0004,
137       KEY_ENUMERATE_SUB_KEYS = 0x0008,
138       KEY_NOTIFY             = 0x0010,
139       KEY_CREATE_LINK        = 0x0020,
140
141       KEY_READ               = (STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY) & ~SYNCHRONIZE,
142       KEY_WRITE              = (STANDARD_RIGHTS_WRITE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY) & ~SYNCHRONIZE,
143       KEY_EXECUTE            = KEY_READ & ~SYNCHRONIZE,
144       KEY_ALL_ACCESS         = (STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK) & ~SYNCHRONIZE,
145     }
146     alias void function(uint dwErrorCode, uint dwNumberOfBytesTransferred, OVERLAPPED* lpOverlapped) OVERLAPPED_COMPLETION_ROUTINE;
147     uint ExpandEnvironmentStringsW(wchar* lpSrc, wchar* lpDst, uint nSize);
148     alias ExpandEnvironmentStringsW ExpandEnvironmentStrings;
149     extern final Handle HKEY_CLASSES_ROOT;
150     extern final Handle HKEY_CURRENT_USER;
151     extern final Handle HKEY_LOCAL_MACHINE;
152     extern final Handle HKEY_USERS;
153     extern final Handle HKEY_PERFORMANCE_DATA;
154     extern final Handle HKEY_CURRENT_CONFIG;
155     extern final Handle HKEY_DYN_DATA;
156     int ReadDirectoryChangesW(Handle hDirectory, void* lpBuffer, uint nBufferLength, int bWatchSubtree, uint dwNotifyFilter, ref uint lpBytesReturned, OVERLAPPED* lpOverlapped, OVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
157     alias ReadDirectoryChangesW ReadDirectoryChanges;
158
159     int RegOpenKeyExW(Handle hKey, wchar* lpSubKey, uint ulOptions, uint samDesired, out Handle phkResult);
160     alias RegOpenKeyExW RegOpenKeyEx;
161
162     int RegQueryValueExW(Handle hKey, wchar* lpValueName, uint* lpReserved, uint* lpType, ubyte* lpData, uint* lpcbData);
163     alias RegQueryValueExW RegQueryValueEx;
164
165     int RegQueryInfoKeyW(Handle hKey, wchar* lpClass, uint* lpcchClass, uint* lpReserved, uint* lpcSubKeys, uint* lpcbMaxSubKeyLen, uint* lpcbMaxClassLen, uint* lpcValues, uint* lpcbMaxValueNameLen, uint* lpcMaxValueLen, uint* lpcbSecurityDescriptor, FILETIME* lpftLastWriteTime);
166     alias RegQueryInfoKeyW RegQueryInfoKey;
167
168     int RegEnumKeyExW(Handle hKey, uint dwIndex, wchar* lpName, uint* lpcchName, uint* lpReserved, wchar* lpClass, uint* lpcchClass, FILETIME* lpftLastWriteTime);
169     alias RegEnumKeyExW RegEnumKeyEx;
170
171     int RegEnumValueW(Handle hKey, uint dwIndex, wchar* lpValueName, uint* lpcchValueName, uint* lpReserved, uint* lpType, ubyte* lpData, uint* lpcbData);
172     alias RegEnumValueW RegEnumValue;
173
174     int RegCreateKeyExW(Handle hKey, wchar* lpSubKey, uint Reserved, wchar* lpClass, uint dwOptions, int samDesired, SECURITY_ATTRIBUTES* lpSecurityAttriubtes, out Handle phkResult, out uint lpsdwDisposition);
175     alias RegCreateKeyExW RegCreateKeyEx;
176
177     int RegCloseKey(Handle hKey);
178
179     int RegFlushKey(Handle hKey);
180
181     int RegDeleteKeyW(Handle hKey, wchar* lpName);
182     alias RegDeleteKeyW RegDeleteKey;
183
184     int RegDeleteValueW(Handle hKey, wchar* lpValueName);
185     alias RegDeleteValueW RegDeleteValue;
186
187     int RegSetValueExW(Handle hKey, wchar* lpValueName, uint Reserved, uint dwType, ubyte* lpData, uint cbData);
188     alias RegSetValueExW RegSetValueEx;
189 }//extern
190
191
192 /*
193 //for tango.sys.win32.UserGdi
194 alias RegQueryInfoKeyW RegQueryInfoKey;
195 alias RegSetValueExW RegSetValueEx;
196 alias RegUnLoadKeyW RegUnLoadKey;
197 alias RegRestoreKeyW RegRestoreKey;
198 alias RegSaveKeyW RegSaveKey;
199 alias RegSetValueW RegSetValue;
200 alias RegQueryValueW RegQueryValue;
201 alias RegQueryMultipleValuesW RegQueryMultipleValues;
202 alias RegQueryValueExW RegQueryValueEx;
203 alias RegReplaceKeyW RegReplaceKey;
204 alias RegConnectRegistryW RegConnectRegistry;
205 alias RegCreateKeyW RegCreateKey;
206 alias RegCreateKeyExW RegCreateKeyEx;
207 alias RegDeleteKeyW RegDeleteKey;
208 alias RegDeleteValueW RegDeleteValue;
209 alias RegEnumKeyW RegEnumKey;
210 alias RegEnumKeyExW RegEnumKeyEx;
211 alias RegEnumValueW RegEnumValue;
212 alias RegLoadKeyW RegLoadKey;
213 alias RegOpenKeyW RegOpenKey;
214 alias RegOpenKeyExW RegOpenKeyEx;
215 alias RegQueryInfoKeyW RegQueryInfoKey;
216 alias HANDLE Handle;
217 */
218
219 alias char[] string;
220
221 // Grabbed from juno.base.string since Tango's stdc.stringz, text.convert.Utf lacks these:
222 public wchar* toString16z(string s, int start = 0, int count = -1) {
223   if (s is null)
224     return null;
225   if (s.length == 0 || count == 0)
226     return "";
227   if (count == -1)
228     count = s.length;
229   return s[start .. count].toUTF16z();
230 }
231 public string toString(wchar* s, int start = 0, int count = -1) {
232   if (s == null)
233     return null;
234   if (count == -1)
235     count = wcslen(cast(dchar*)s);
236   return s[start .. count].toUTF8();
237 }
238 /**
239  * Returns a string array containing the substrings in s that are delimited by elements of the specified char array.
240  * Params:
241  *   s = The string to _split.
242  *   separator = An array of characters that delimit the substrings in s.
243  *   count = The maximum number of substrings to return.
244  *   removeEmptyEntries = true to omit empty array elements from the array returned, or false to include empty array elements in the array returned.
245  * Returns: An array whose elements contain the substrings in s that are delimited by one or more characters in separator.
246  */
247 public string[] split(string s, char[] separator, int count = int.max, bool removeEmptyEntries = false) {
248
249   int createSeparatorList(ref int[] sepList) {
250     int foundCount;
251     if (separator.length == 0) {
252       for (int i = 0; i < s.length && foundCount < sepList.length; i++) {
253         if (isWhiteSpace(s[i]))
254           sepList[foundCount++] = i;
255       }
256     }
257     else {
258       for (int i = 0; i < s.length && foundCount < sepList.length; i++) {
259         for (int j = 0; j < separator.length; j++) {
260           if (s[i] == separator[j]) {
261             sepList[foundCount++] = i;
262             break;
263           }
264         }
265       }
266     }
267     return foundCount;
268   }
269
270   if (count == 0 || (removeEmptyEntries && s.length == 0))
271     return new string[0];
272
273   int[] sepList = new int[s.length];
274   int replaceCount = createSeparatorList(sepList);
275
276   if (replaceCount == 0 || count == 1)
277     return [ s ];
278
279   string[] splitStrings;
280   int arrayIndex, currentIndex;
281
282   if (removeEmptyEntries) {
283     int max = (replaceCount < count) ? replaceCount + 1 : count;
284     splitStrings.length = max;
285     for (int i = 0; i < replaceCount && currentIndex < s.length; i++) {
286       if (sepList[i] - currentIndex > 0)
287         splitStrings[arrayIndex++] = s[currentIndex .. sepList[i]];
288       currentIndex = sepList[i] + 1;
289       if (arrayIndex == count - 1) {
290         while (i < replaceCount - 1 && currentIndex == sepList[++i]) {
291           currentIndex += 1;
292         }
293         break;
294       }
295     }
296
297     if (currentIndex < s.length)
298       splitStrings[arrayIndex++] = s[currentIndex .. $];
299
300     string[] strings = splitStrings;
301     if (arrayIndex != max) {
302       strings.length = arrayIndex;
303       for (int j = 0; j < arrayIndex; j++)
304         strings[j] = splitStrings[j];
305     }
306     splitStrings = strings;
307   }
308   else {
309     count--;
310     int max = (replaceCount < count) ? replaceCount : count;
311     splitStrings.length = max + 1;
312     for (int i = 0; i < max && currentIndex < s.length; i++) {
313       splitStrings[arrayIndex++] = s[currentIndex .. sepList[i]];
314       currentIndex = sepList[i] + 1;
315     }
316
317     if (currentIndex < s.length && max >= 0)
318       splitStrings[arrayIndex] = s[currentIndex .. $];
319     else if (arrayIndex == max)
320       splitStrings[arrayIndex] = null;
321   }
322
323   return splitStrings;
324 }
325 private final char[] WhiteSpaceChars = [ '\t', '\n', '\v', '\f', '\r', ' ' ];
326
327 /**
328  * Indicates whether the specified character is white space.
329  * Params: c = A character.
330  * Returns: true if c is white space; otherwise, false.
331  */
332 public bool isWhiteSpace(char c) {
333   foreach (ch; WhiteSpaceChars) {
334     if (ch == c)
335       return true;
336   }
337   return false;
338 }
339
340
341     wchar* toUTF16z(char[] s)
342     {
343         wchar[] ws;
344         ws = toString16(s);
345         ws ~= '\0';
346         return ws.ptr;
347     }
348    
349
350 public string expandEnvironmentVariables(string name) {
351   string[] parts = name.split(['%']);
352
353   int c = 100;
354   wchar[] buffer = new wchar[c];
355   for (int i = 1; i < parts.length - 1; i++) {
356     if (parts[i].length > 0) {
357       string temp = "%" ~ parts[i] ~ "%";
358       uint n = ExpandEnvironmentStrings(temp.toString16z(), buffer.ptr, c);
359       while (n > c) {
360         c = n;
361         buffer.length = c;
362         n = ExpandEnvironmentStrings(temp.toString16z(), buffer.ptr, c);
363       }
364     }
365   }
366   int n = ExpandEnvironmentStrings(name.toString16z(), buffer.ptr, c);
367   while (n > c) {
368     c = n;
369     buffer.length = c;
370     n = ExpandEnvironmentStrings(name.toString16z(), buffer.ptr, c);
371   }
372
373   return .toString(buffer.ptr);
374 }
375
376
377 /**
378  * Identifies the data type of a value in the registry.
379  */
380 public enum RegistryValueKind {
381   Unknown = 0,        /// Indicates an unsupported registry data type.
382   String = 1,         /// Specifies a string. Equivalent to REG_SZ.
383   ExpandString = 2,   /// Specifies a string containing references to environment variables. Equivalent to REG_EXPAND_SZ.
384   Binary = 3,         /// Specifies binary data in any form. Equivalent to REG_BINARY.
385   DWord = 4,          /// Specifies a 32-bit binary number. Equivalent to REG_DWORD.
386   MultiString = 7,    /// Specifies an array of strings. Equivalent to REG_MULTI_SZ.
387   QWord = 11          /// Specifies a 64-bit binary number. Equivalent to REG_QWORD.
388 }
389
390 /**
391  * Represents a node in the Windows registry.
392  */
393 public final class RegistryKey {
394
395   private const string[] HKEY_NAMES = [
396     "HKEY_CLASSES_ROOT", "HKEY_CURRENT_USER", "HKEY_LOCAL_MACHINE", "HKEY_USERS", "HKEY_PERFORMANCE_DATA", "HKEY_CURRENT_CONFIG", "HKEY_DYN_DATA"
397   ];
398
399   public static const RegistryKey classesRoot;      /// Defines the types of documents and properties associated with those types. Reads HKEY_CLASSES_ROOT.
400   public static const RegistryKey currentUser;      /// Contains information about the current user preferences. Reads HKEY_CURRENT_USER.
401   public static const RegistryKey localMachine;     /// Contains configuration data for the local machine. Reads HKEY_LOCAL_MACHINE.
402   public static const RegistryKey users;            /// Contains information about the default user configuration. Reads HKEY_USERS.
403   public static const RegistryKey performanceData;  /// Contains performance information for software components. Reads HKEY_PERFORMANCE_DATA.
404   public static const RegistryKey currentConfig;    /// Contains configuration information about hardware that is not specifiec to the user. Reads HKEY_CURRENT_CONFIG.
405   public static const RegistryKey dynData;          /// Contains dynamic registry data. Reads HKEY_DYN_DATA.
406
407   private Handle hKey_;
408   private string name_;
409   private bool systemKey_;
410   private bool perfDataKey_;
411   private bool writable_;
412   private bool remoteKey_;
413   private bool dirty_;
414
415   static this() {
416     classesRoot = get(cast(Handle)HKEY_CLASSES_ROOT);
417     currentUser = get(cast(Handle)HKEY_CURRENT_USER);
418     localMachine = get(cast(Handle)HKEY_LOCAL_MACHINE);
419     users = get(cast(Handle)HKEY_USERS);
420     performanceData = get(cast(Handle)HKEY_PERFORMANCE_DATA);
421     currentConfig = get(cast(Handle)HKEY_CURRENT_CONFIG);
422     dynData = get(cast(Handle)HKEY_DYN_DATA);
423   }
424
425   ~this() {
426     close();
427   }
428
429   /**
430    * Retrieves a string representation of this key.
431    * Returns: A string representing the key.
432    */
433   //public override string toString() {
434   public string toString() {
435     return name_;
436   }
437
438   /**
439    * Closes the key.
440    */
441   public void close() {
442     if (hKey_ != Handle.init) {
443       if (!systemKey_) {
444         RegCloseKey(hKey_);
445         hKey_ = Handle.init;
446       }
447     }
448   }
449
450   /**
451    * Writes all the attributes of this key to the registry.
452    */
453   public void flush() {
454     if (hKey_ != Handle.init && dirty_)
455       RegFlushKey(hKey_);
456   }
457
458   /**
459    * Retrieves a subkey.
460    * Params:
461    *   name = Name or path of the subkey to open.
462    *   writable = true if you need write access to the key.
463    * Returns: The subkey requested, or null if the operation failed.
464    */
465   public RegistryKey openSubKey(string name, bool writable = false) {
466     Handle hkey;
467     int r = RegOpenKeyEx(hKey_, name.toString16z(), 0, (writable ? (KEY_READ | KEY_WRITE) : KEY_READ), hkey);
468     if (r == ERROR_SUCCESS && hkey != Handle.init && hkey != INVALID_HANDLE_VALUE) {
469       auto key = new RegistryKey(hkey, writable, false, false, false);
470       key.name_ = this.name_ ~ '\\' ~ name;
471       return key;
472     }
473     //if (r == ERROR_ACCESS_DENIED)
474       //issueError(r, name_ ~ '\\' ~ name);
475     return null;
476   }
477
478   public RegistryKey createSubKey(string name) {
479     Handle hkey;
480     uint disposition;
481     int r = RegCreateKeyEx(hKey_, name.toString16z(), 0, null, 0, KEY_READ | KEY_WRITE, null, hkey, disposition);
482     if (r == ERROR_SUCCESS && hkey != Handle.init && hkey != INVALID_HANDLE_VALUE) {
483       auto key = new RegistryKey(hkey, true, false, false, false);
484       if (name.length == 0)
485         key.name_ = name_;
486       else
487         key.name_ = name_ ~ '\\' ~ name;
488       return key;
489     }
490     //if (r != ERROR_SUCCESS)
491       //issueError(r, name_ ~ '\\' ~ name);
492     return null;
493   }
494
495   /**
496    * Deletes the specified subkey.
497    * Params: name = The _name of the subkey to delete.
498    */
499   public void deleteSubKey(string name) {
500     RegDeleteKey(hKey_, name.toString16z());
501   }
502
503   public void deleteSubKeyTree(string name) {
504     scope key = openSubKey(name, true);
505     if (key !is null) {
506       if (key.subKeyCount > 0) {
507         foreach (subkey; key.subKeyNames)
508           key.deleteSubKeyTree(subkey);
509       }
510
511       key.close();
512
513       int r = RegDeleteKey(hKey_, name.toString16z());
514       //if (r != ERROR_SUCCESS)
515         //issueError(r, null);
516     }
517     else
518       throw new RegistryException("Cannot delete a subkey tree because the subkey does not exist.");
519   }
520
521   /**
522    * Deletes the specified value from the registry.
523    * Params: name = The _name of the value to delete.
524    */
525   public void deleteValue(string name) {
526     RegDeleteValue(hKey_, name.toString16z());
527   }
528
529   /**
530    * Retrieves the registry data type of the value associated with the specified _name.
531    * Params: name = The _name of the value whose registry data type is to be retrieved.
532    * Returns: A value representing the registry data type of the value associated with name.
533    */
534   public RegistryValueKind getValueKind(string name) {
535     uint cb, type;
536     RegQueryValueEx(hKey_, name.toString16z(), null, &type, null, &cb);
537     return cast(RegistryValueKind)type;
538   }
539
540   /**
541    * Retrieves the value associated with the specified _name.
542    * Params:
543    *   name = The _name of the value to retrieve.
544    *   defaultValue = The value to return if name does not exist.
545    *   doNotExpand = Specify false to expand environment values.
546    * Returns: The value associated with name, or defaultValue if name is not found.
547    */
548   public T getValue(T)(string name, T defaultValue = T.init, bool doNotExpand = false) {
549     uint cb, type;
550     if (RegQueryValueEx(hKey_, name.toString16z(), null, &type, null, &cb) == 0) {
551       static if (is(T == int)) {
552         if (type == REG_DWORD) {
553           int b;
554           RegQueryValueEx(hKey_, name.toString16z(), null, &type, cast(ubyte*)&b, &cb);
555           return b;
556         }
557       }
558       else static if (is(T == long)) {
559         if (type == REG_QWORD) {
560           long b;
561           RegQueryValueEx(hKey_, name.toString16z(), null, &type, cast(ubyte*)&b, &cb);
562           return b;
563         }
564       }
565       else static if (is(T : string)) {
566         if (type == REG_SZ || type == REG_EXPAND_SZ) {
567           wchar[] b = new wchar[cb / 2];
568           RegQueryValueEx(hKey_, name.toString16z(), null, &type, cast(ubyte*)b.ptr, &cb);
569           string result = .toString(b.ptr);
570
571           if (!doNotExpand && type == REG_EXPAND_SZ)
572             return expandEnvironmentVariables(result);
573
574           return result;
575         }
576       }
577       else static if (is(T : string[])) {
578         if (type == REG_MULTI_SZ) {
579           string[] result;
580
581           wchar[] b = new wchar[cb / 2];
5