Jump to content
tukangusil7

Is it possible to get the whole string containing null characters from array of WCHARs?

Recommended Posts

tukangusil7

Consider the following example:

Local $tsData
Local $sData
$tsData = DllStructCreate("WCHAR[8]")
DllStructSetData($tsData, 1, "123" & ChrW(0) & ChrW(0) & "678")
$sData = DllStructGetData($tsData, 1)
MsgBox(0, $sData, StringLen($sData))

The value of $sData from the above example will be "123".

Is there a way to make $sData copies the all characters from the array of WCHARs? Perhaps by making use the RtlMoveMemory function (if possible) to copy the contents of WCHARs directly to $sData, but the problem is how to allocate a certain length for $sData and get a pointer to it. Sorry, I'm a newbie in AutoIt world.

Edited by tukangusil7

Share this post


Link to post
Share on other sites
jchd

Nul bytes in a string are treated as string termination in a certain number of cases and DllStructGetData is one of them.

You might expect that overlaying a ushort[8]  DllStrucct above $tsData would solve the problem by allowing you to retrieve the whole ushort array with DllStructGetData. Unfortunately this doesn't work and you only fetch the first ushort of the array if you don't specify an element index. You can still fetch all the elements in a For loop.

So yes copying the memory is likely to will work better.

  • Thanks 1

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Share this post


Link to post
Share on other sites
tukangusil7

Inspired by jchd's statement, one way that I have just found to solve the problem is by doing the following steps:

  1.  Define the array of WCHARs as an array of BYTEs.
  2.  Call DllStructGetData() to get the bytes. In this case, DllStructGetData() will store the bytes as a binary variant.
  3.  Call BinaryToString() to convert the binary variant to string.

By doing so, the final string copies the whole data including the null characters. See the code below.

However, any better approach will be appreciated.

#include <Array.au3>

Func Test()
  Local $tsData
  Local $binData
  Local $sData

  ; Just for an example,
  ;   $tsData is set to "123" & ChrW(0) & ChrW(0) & "678"
  $tsData = DllStructCreate("BYTE[16]")
  DllStructSetData($tsData, 1, 49, 1)   ; 49 is code for "1"
  DllStructSetData($tsData, 1, 50, 3)   ; 50 is code for "2"
  DllStructSetData($tsData, 1, 51, 5)   ; 51 is code for "3"
  DllStructSetData($tsData, 1, 00, 7)   ; ChrW(0)
  DllStructSetData($tsData, 1, 00, 9)   ; ChrW(0)
  DllStructSetData($tsData, 1, 54, 11)  ; 54 is code for "6"
  DllStructSetData($tsData, 1, 55, 13)  ; 55 is code for "7"
  DllStructSetData($tsData, 1, 56, 15)  ; 56 is code for "8"

  $binData = DllStructGetData($tsData, 1)  ; The array of bytes becomes a Binary variant
  $sData = BinaryToString($binData, $SB_UTF16LE)  ; Converts the binary variant to string

  ; Since MsgBox() cannot be used to verify a string containing null characters, in order to
  ; verify whether $sData contains the whole string including the null characters, I convert
  ; $sData to an array of Unicode code points.
  $abtData = StringToASCIIArray($sData)  ; $abtData holds the code units contained in $sData

  _ArrayDisplay($abtData, StringLen($sData))
EndFunc

Test()

The background story to this question is that I'm creating a wrapper function for NtQueryValueKey(). If we choose KEY_VALUE_FULL_INFORMATION struct to hold the result of this function, the Name member of KEY_VALUE_FULL_INFORMATION struct will contain the specified value name plus the value data. Both information is contained in one string. To make this even more complicated, if the value being queried is of type REG_MULTI_SZthe value data part will be a sequence of null-terminated strings.

Edited by tukangusil7

Share this post


Link to post
Share on other sites
jchd

It's OK, WCHAR is a 16-bit entity.


This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Share this post


Link to post
Share on other sites
tukangusil7

OK, since my proposal in the bug tracker was declined, I want to share my UDF that converts an array of bytes in a struct to a string.

StructByteArrayToString.au3 >

#include-once
#include <StringConstants.au3>

Opt("MustDeclareVars", 1)

;---------------------------------------------------------------------------------------------------
; Purpose: Converts an array of bytes in a struct to a string. The array of bytes is returned by a
;   call to an API function that informs the length of the bytes that has been written to the array.
; Parameters:
;   $paiBytes [in]: A pointer to an array of bytes in an element of a struct. The array of bytes is
;     assumed to be an array of UTF-16 Little Endian code units. For example, to get a pointer to
;     the array of bytes in the 1st element of the following struct,
;       $taiBuffStr = DllStructCreate("BYTE[1024]")
;     set $paiBytes parameter to DllStructGetPtr($taiBuffStr, 1).
;   $iBytesLen [in]: The actual length of the bytes in the array. This actual bytes length is given
;     by the corresponding API function.
;   $bIncludeNullTerminator [in,opt]: Specifies whether the returned string will include a null
;     terminator, if any, in the source string. If this parameter is set to False (default) and the
;     source string is a registry value data of type $REG_MULTI_SZ, the last two null terminators
;     of the source string are excluded from retrieval.
; Return: String
;   - Success: The corresponding string.
;   - Failure: ""; @error flag is set to error code.
; Background: When DllStructGetData() retrieves a WCHAR array that contains null characters, the
;   returned string is truncated at the first encountered null character. Fortunately, there is a
;   workaround to this problem, that is by doing the following steps:
;     1. Define the WCHAR array as a BYTE array;
;     2. Call DllStructGetData() to get the bytes. In this case, DllStructGetData() will store the
;        bytes as a binary variant.
;     3. Call BinaryToString() to convert the binary variant to string.
; Author: Sayyid Ibnu Husein Alatas
; Last Updated on 22 April 2018
;---------------------------------------------------------------------------------------------------
Func StructByteArrayToString($paiBytes, $iBytesLen, $bIncludeNullTerminator = 0)
  Local Const $ciSizeOfWChar = 2
  Local $tiLastCharCode
  Local $iLastCharCode
  Local $taiBytes
  Local $bvStr

  If ($paiBytes = Null) Or ($iBytesLen <= 0) Then Return SetError(1, 0, "")

  If Not $bIncludeNullTerminator Then
    ; Grab the last character code unit from the array of bytes
    $tiLastCharCode = DllStructCreate("WORD[1]", $paiBytes + $iBytesLen - $ciSizeOfWChar)
    $iLastCharCode = DllStructGetData($tiLastCharCode, 1)

    ; If the last character is a null terminator, exclude it from retrieval
    If $iLastCharCode = 0 Then
      $iBytesLen -= $ciSizeOfWChar
      ; If the second-last character is also a null terminator, exclude it too from retrieval.
      ; Actually this is intended for registry value data of type $REG_MULTI_SZ.
      If $iBytesLen >= (2 * $ciSizeOfWChar) Then
        $tiLastCharCode = DllStructCreate("WORD[1]", $paiBytes + $iBytesLen - $ciSizeOfWChar)
        $iLastCharCode = DllStructGetData($tiLastCharCode, 1)
        If $iLastCharCode = 0 Then $iBytesLen -= $ciSizeOfWChar
      EndIf
    EndIf  ; $iLastCharCode = 0
  EndIf  ; Not $bIncludeNullTerminator

  ; Retrieve the bytes and return it as a string
  $taiBytes = DllStructCreate("BYTE[" & $iBytesLen & "]", $paiBytes)
  $bvStr = DllStructGetData($taiBytes, 1)  ; Take the bytes data as a binary variant
  Return BinaryToString($bvStr, $SB_UTF16LE)  ; Return the binary variant as a string
EndFunc  ; StructByteArrayToString
;===================================================================================================

TestStructByteArrayToString.au3 >

#include <Array.au3>
#include "StructByteArrayToString.au3"

Opt("MustDeclareVars", 1)

;---------------------------------------------------------------------------------------------------
Func Test()
  Local Const $ciSizeOfWChar = 2
  Local $taiBuffStr = DllStructCreate("BYTE[1024]")
  Local $sStr = "123" & ChrW(0) & ChrW(0) & "678" & ChrW(0)
  Local $bvStr = StringToBinary($sStr, $SB_UTF16LE)
  DllStructSetData($taiBuffStr, 1, $bvStr)

  Local $paiBytes = DllStructGetPtr($taiBuffStr, 1)
  Local $sResult = StructByteArrayToString($paiBytes, StringLen($sStr) * $ciSizeOfWChar, False)
  Local $aiBytes = StringToASCIIArray($sResult)
  _ArrayDisplay($aiBytes)
EndFunc
;===================================================================================================

Test()

Good luck!

Edited by tukangusil7

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×