Issue #252: Smalltak/X is writing Windows Registry only in ASCII but registry is UTF16 jv
authorPatrik Svestka <patrik.svestka@gmail.com>
Wed, 05 Dec 2018 14:21:11 +0100
branchjv
changeset 23758 8fcc24554bc7
parent 23614 1c6f89bd0de6
child 23759 3db8d48a8cb1
Issue #252: Smalltak/X is writing Windows Registry only in ASCII but registry is UTF16 - created new method valueNamed:put:unexpandedReferences: - enables the user to create registry type REG_EXPAND_SZ if needed - now unicode (UTF-16) writing with all the registry types covered in tests in RegressionTests:Win32OperatingSystemTest - the datapointer is now void *datapointer type (from unsigned char) - removed the superfluous casts to (unsigned char *) - coververting name and data (that needs it) to name and data with null termination - using memcpy instead of strcpy to ignore any NULL characters in unicode - the previous method valueNamed:put: now calls the valueNamed:put:unexpandedReferences: with the unexpandedReferences: being always false minor refactoring: - defaultValue: datum -> defaultValue: data
Win32OperatingSystem.st
--- a/Win32OperatingSystem.st	Fri Jan 18 11:57:14 2019 +0000
+++ b/Win32OperatingSystem.st	Wed Dec 05 14:21:11 2018 +0100
@@ -16533,7 +16533,7 @@
     "
 !
 
-defaultValue:datum
+defaultValue:data
     "store a value; the value type depends upon the stored value:
 	ByteArray       -> REG_BINARY
 	String          -> REG_SZ
@@ -16542,7 +16542,7 @@
 	nil             -> REG_NONE
     "
 
-    ^ self valueNamed:'' put:datum
+    ^ self valueNamed:'' put:data
 
     "
      (self key:'HKEY_CLASSES_ROOT\MicrosoftWorks.WordProcessor\CLSID') defaultValue
@@ -16923,16 +16923,62 @@
     "
 !
 
-valueNamed:aValueName put:datum
+valueNamed:name put:data
+    "store a value; the value type depends upon the stored value:
+    ByteArray       -> REG_BINARY
+    String          -> REG_SZ
+    Array of string -> REG_MULTI_SZ
+    Integer         -> REG_DWORD
+    nil             -> REG_NONE
+    "
+    "Transcript showCR: 'name:', name.
+    Transcript showCR: 'data:', data."
+    
+    ^ self valueNamed:name put:data unexpandedReferences:false
+!
+
+valueNamed:name put:data unexpandedReferences:containsUnexpandedReferences
     "store a value; the value type depends upon the stored value:
 	ByteArray       -> REG_BINARY
-	String          -> REG_SZ
+	String          -> REG_SZ OR REG_EXPAND_SZ based on containsUnexpandedReferences
 	Array of string -> REG_MULTI_SZ
 	Integer         -> REG_DWORD
 	nil             -> REG_NONE
-    "
-
-    |data stringArray errorNumber|
+
+    containsUnexpandedReferences:
+        true -> REG_EXPAND_SZ
+        false -> REG_SZ
+
+    REG_EXPAND_SZ:
+    Null-terminated string that contains unexpanded references to environment variables (for example, ""%PATH%""). It will be a Unicode or ANSI string, 
+    depending on whether you use the Unicode or ANSI functions.
+    "
+
+    |nameUtf16Z dataUtf16Z stringArray errorNumber|
+
+    "/ name must be a string, if not return nil
+    name isString ifFalse: [ 
+        Transcript showCR: 'The registry value name must be a String!!'.
+        ^ nil
+    ].
+    "/ name asUnicodeString and null terminated
+    name notNil ifTrue:[
+        name isEmpty ifTrue:[nameUtf16Z := (name, (Character codePoint: 0)) asUnicode16String] "/ needed for defaultValue:
+                    ifFalse:[nameUtf16Z := name asUnicode16StringZ]
+    ].
+   "/ data asUnicode16String and null terminated
+    data notNil ifTrue:[ 
+        data isString ifTrue:[
+            data isEmpty ifTrue:[dataUtf16Z := (data, (Character codePoint: 0)) asUnicode16String] "/ for empty data strings
+                        ifFalse:[dataUtf16Z := data asUnicode16StringZ]
+        ].
+        data isArray ifTrue:[ 
+            dataUtf16Z := data collect:[:eachString | "/ for every Array member to be null terminated
+                eachString asUnicode16StringZ
+            ]
+        ]
+    ].
+
 %{
     /* Registry Element Size Limits
      * 
@@ -16952,48 +16998,90 @@
      * another backslash as an escape character. For example, specify "C:\\mydir\\myfile" to store the string "C:\mydir\myfile". 
      * A file location can also be the name of a key if it is within the 255-character limit for key names and does not contain backslashes,
      * which are not allowed in key names.
+     *
+     * RegSetValueExW (catches)
+     *
+     * LSTATUS RegSetValueExW(
+     *   HKEY       hKey,
+     *   LPCSTR     lpValueName,
+     *   DWORD      Reserved,
+     *   DWORD      dwType,
+     *   const BYTE *lpData,
+     *   DWORD      cbData
+     * );
+     * 
+     * lpValueName
+     * The name of the value to be set. If a value with this name is not already present in the key, the function adds it to the key.
+     * If lpValueName is NULL or an empty string, "", the function sets the type and data for the key's unnamed or default value.
+     * For more information, see Registry Element Size Limits (above).
+     * Registry keys do not have default values, but they can have one unnamed value, which can be of any type.
+     *
+     * lpData
+     * The data to be stored.
+     * For string-based types, such as REG_SZ, the string must be null-terminated. With the REG_MULTI_SZ data type, the string must be terminated
+     * with **two** null characters.
+     * Note  lpData indicating a null value is valid, however, if this is the case, cbData must be set to '0'.
+     * 
+     * cbData
+     * The size of the information pointed to by the lpData parameter, in bytes. 
+     * If the data is of type REG_SZ, REG_EXPAND_SZ, or REG_MULTI_SZ, cbData must include the size of the terminating null character or characters.
+     *
+     * Remarks
+     * Value sizes are limited by available memory. However, storing large values in the registry can affect its performance. 
+     * Long values (more than 2,048 bytes) should be stored as files, with the locations of the files stored in the registry.
+     *
+     * Application elements such as icons, bitmaps, and executable files should be stored as files and not be placed in the registry.
+     * 
+     * If dwType is the REG_SZ, REG_MULTI_SZ, or REG_EXPAND_SZ type and the ANSI version of this function is used (either by explicitly 
+     * calling RegSetValueExA or by not defining UNICODE before including the Windows.h file), the data pointed to by the lpData parameter 
+     * must be an ANSI character string. The string is converted to Unicode before it is stored in the registry.
+     *
+     * Note that operations that access certain registry keys are redirected. For more information, see Registry Virtualization and 32-bit and 
+     * 64-bit Application Data in the Registry.
+     *
      */
 
     HKEY myKey;
     DWORD valueType = -1;
     int val;
+    ULONGLONG longVal;
     DWORD dataSize = -1;
-    unsigned char *dataPointer = NULL;
-    int datumOk = 1, mustFreeData = 0;
+    void *dataPointer = NULL;
+    int dataOk = 1, mustFreeData = 0;
 
     if (__isExternalAddressLike(__INST(handle))
-     && __isStringLike(aValueName)) {
+      && __isUnicode16String(nameUtf16Z)) {
 	int ret;
 	OBJ cls;
 
 	myKey = (HKEY)__externalAddressVal(__INST(handle));
 
-	if (datum == nil) {
+	if (data == nil) {
 	    valueType = REG_NONE;
 	    dataSize = 0;
-	} else if (__isSmallInteger(datum)) {
+	} else if (__isSmallInteger(data)) {
 	    valueType = REG_DWORD;
-	    val = __intVal(datum);
-	    dataPointer = (unsigned char *)(&val);
+	    val = __intVal(data);
+	    dataPointer =  &val;
 	    dataSize = sizeof(val);
-	} else if (__isStringLike(datum)) {
-	    valueType = REG_SZ;
-	    dataPointer = __stringVal(datum);
-	    dataSize = __stringSize(datum) + 1;
-	} else if (__Class(datum) == ByteArray) {
+	} else if (__isUnicode16String(dataUtf16Z)) {
+        valueType = containsUnexpandedReferences == true ? REG_EXPAND_SZ : REG_SZ;
+        dataPointer = __unicode16StringVal(dataUtf16Z);
+    	dataSize = __unicode16StringSize(dataUtf16Z) * sizeof(wchar_t);
+	} else if (__Class(data) == ByteArray) {
 	    valueType = REG_BINARY;
-	    dataPointer = __ByteArrayInstPtr(datum)->ba_element;
-	    dataSize = __byteArraySize(datum);
-	} else if (__Class(datum) == LargeInteger) {
-	    valueType = REG_DWORD;
-	    val = __longIntVal(datum);
-	    if (val) {
-		dataPointer = (unsigned char *)(&val);
-		dataSize = sizeof(val);
+	    dataPointer = __ByteArrayInstPtr(data)->ba_element;
+	    dataSize = __byteArraySize(data);
+	} else if (__Class(data) == LargeInteger) {
+	    valueType = REG_QWORD;
+	    longVal = __longIntVal(data);
+	    if (longVal) {
+    		dataPointer = &longVal;
+    		dataSize = sizeof(longVal);
 	    } else {
-		datumOk = 0;
-	    }
-	} else if (__Class(datum) == Array) {
+		    dataOk = 0;
+	    }
+	} else if (__Class(data) == Array) {
 	    int i = 0, ns = 0, totalSize = 0;
 
 	    valueType = REG_MULTI_SZ;
@@ -17002,45 +17090,48 @@
 	     * must allocate a local buffer
 	     * find size ...
 	     */
-	    for (i=0; i<__arraySize(datum); i++) {
-		OBJ s = __ArrayInstPtr(datum)->a_element[i];
-
-		if (__isStringLike(s)) {
-		    totalSize += __stringSize(s) + 1;
+	    for (i=0; i<__arraySize(dataUtf16Z); i++) {
+		OBJ s = __ArrayInstPtr(dataUtf16Z)->a_element[i];
+
+		if (__isUnicode16String(s)) {
+		    totalSize += __unicode16StringSize(s);
 		} else {
-		    datumOk = 0;
+		    dataOk = 0;
 		    break;
 		}
 		ns++;
 	    }
-	    if (datumOk) {
-		char *cp;
+
+	    if (dataOk) {
+		wchar_t *cp;
 
 		/*
 		 * allocate and fill...
 		 */
-		totalSize ++;
-		dataPointer = (char *)(malloc(totalSize));
+        // (totalSize length * size of wide char) + terminating null character
+        totalSize = (totalSize * sizeof(wchar_t)) + 1;
+        dataPointer = malloc(totalSize);
 		mustFreeData = 1;
 		cp = dataPointer;
-		for (i=0; i<__arraySize(datum); i++) {
-		    OBJ s = __ArrayInstPtr(datum)->a_element[i];
-
-		    strcpy(cp, __stringVal(s));
-		    cp += __stringSize(s);
-		    *cp++ = '\0';
+		for (i=0; i<__arraySize(dataUtf16Z); i++) {
+		    OBJ s = __ArrayInstPtr(dataUtf16Z)->a_element[i];
+
+            memcpy(cp, __unicode16StringVal(s), __unicode16StringSize(s) * sizeof(wchar_t));
+		    cp += __unicode16StringSize(s);
 		}
-		*cp++ = '\0';
+		*cp++ = '\0'; // adding second terminating null character (for more information see MSDN comment above)
 		dataSize = totalSize;
+        //console_printf("DataSize: %d\n", dataSize);  
 	    }
 	} else {
-	    datumOk = 0;
-	}
-
-	if (datumOk) {
-	    ret = RegSetValueExA(myKey, __stringVal(aValueName),
+	    dataOk = 0;
+	}
+
+	if (dataOk) {
+	    ret = RegSetValueExW(myKey, __unicode16StringVal(nameUtf16Z),
 				0, valueType,
-				dataPointer, dataSize);
+				(LPBYTE) dataPointer,
+                dataSize);
 	    if (mustFreeData) {
 		free(dataPointer);
 	    }