Jump to content

Handling the equivalent of MULTI_SZ strings (not from registry)


Mbee
 Share

Recommended Posts

I'm writing up the interface code for _WinAPI_GetVolumePathNamesForVolumeName(). Here's a link to it's MSDN page: GetVolumePathNamesForVolumeName

The primary return from that function is array/string of substrings that are each zero-terminated, and the entire thing is terminated by double zero.  It would be easy to separate the substrings with built-in AutoIt functions, except I can't imagine that such a multi-string will survive the first operation; I think the first time I try to use that multi-string, it will be truncated at the first zero.  And that first operation, to my limited knowledge, will be when I try to extract the multi-string from the DLL return structure.

Here's what I coded for the interface function...

Local $Lf_RetStruct = DllCall('kernel32.dll', 'bool', 'GetVolumePathNamesForVolumeNameW', 'wstr', $Lf_VolGUIDStr, _
                                        'wstr', $Lf_VolNamesBufAra, 'dword', 255, 'dword', $Lf_VolNamesBufAraLen )

Maybe if I change that second 'wstr' into something else I could treat it as a character or byte array?  But I don't know enough to know what to replace it with, or how to avoid truncation at the first zero.

Of course, I'm open to any suggestion for handling multi-strings...  Thanks!

Link to comment
Share on other sites

Those single null separated double null terminated strings have always been a pain

Spoiler

Old Code:

#include <WinAPIFiles.au3>
Global $sMountedPath = "c:\"
Global $sGuid = _WinAPI_GetVolumeNameForVolumeMountPoint($sMountedPath)
ConsoleWrite("Vol Guid: " & $sGuid & @CRLF)
ConsoleWrite("Mount Point(s): " & __WinAPI_GetVolumePathNamesForVolumeName($sGuid) & @CRLF)

Func __WinAPI_GetVolumePathNamesForVolumeName($sVolumeGUID)

    Local $tVolNames, $tSize = DllStructCreate('int iSize')

    ;First call to get size of buffer req'd
    Local $aRet = DllCall('kernel32.dll', 'bool', 'GetVolumePathNamesForVolumeNameW', 'wstr', $sVolumeGUID, 'ptr', 0, 'dword', 0, 'ptr', DllStructGetPtr($tSize))

    $tVolNames = DllStructCreate("byte [" & DllStructGetData($tSize, 1) * 2 & "]") ;Multiply by 2 since TChars are 2 bytes each as far as Autoit is concerned

    ;Second call gets the actual data
    $aRet = DllCall('kernel32.dll', 'bool', 'GetVolumePathNamesForVolumeNameW', 'wstr', $sVolumeGUID, _
            'ptr', DllStructGetPtr($tVolNames), 'dword', DllStructGetSize($tVolNames), 'ptr', DllStructGetPtr($tSize))
    If @error Or Not $aRet[0] Then Return SetError(@error + 10, @extended, '')

    ;Step through the data turn the nulls into invalid unicode char (0xFFFD) @jchd
    For $i = 1 To DllStructGetSize($tVolNames) Step 2 ;skip every other byte
    
        ;(Pointers (offset) is zero based where as DllStruct indices are 1-based)
        $tChar = DllStructCreate("ushort", DllStructGetPtr($tVolNames) + $i - 1) ; Load 2 bytes into struct (8 bits * 2 = 16 bits = ushort)
        
        If DllStructGetData($tChar, 1) = 0x0000 Then DllStructSetData($tChar, 1, 0xFFFD)
    Next

    Return StringTrimRight(BinaryToString(DllStructGetData($tVolNames, 1), $SB_UTF16LE), 2) ;discard last 2 (',,')
EndFunc   ;==>__WinAPI_GetVolumePathNamesForVolumeName

 

#include <WinAPIFiles.au3>
Global $sMountedPath = "c:\" ; Don't forget to end with final '\'
Global $sGuid = _WinAPI_GetVolumeNameForVolumeMountPoint($sMountedPath)
ConsoleWrite("Vol Guid: " & $sGuid & @CRLF)
ConsoleWrite("Mount Point(s): " & __WinAPI_GetVolumePathNamesForVolumeName($sGuid) & @CRLF)

Func __WinAPI_GetVolumePathNamesForVolumeName($sVolumeGUID)

    Local $tVolNames, $tSize = DllStructCreate('int iSize')

    ;First call to get size of buffer req'd
    Local $aRet = DllCall('kernel32.dll', 'bool', 'GetVolumePathNamesForVolumeNameW', 'wstr', $sVolumeGUID, 'ptr', 0, 'dword', 0, 'ptr', DllStructGetPtr($tSize))

    $tVolNames = DllStructCreate("wchar [" & DllStructGetData($tSize, 1) & "]")

    ;Second call gets the actual data
    $aRet = DllCall('kernel32.dll', 'bool', 'GetVolumePathNamesForVolumeNameW', 'wstr', $sVolumeGUID, _
            'ptr', DllStructGetPtr($tVolNames), 'dword', DllStructGetSize($tVolNames), 'ptr', DllStructGetPtr($tSize))
    If @error Or Not $aRet[0] Then Return SetError(@error + 10, @extended, '')

    ;Step through the data turn the nulls into invalid unicode char (0xFFFD) @jchd
    For $i = 1 To (DllStructGetSize($tVolNames)/2) - 2 ;each character occupies 2 bytes and we ignore the last two characters
        If DllStructGetData($tVolNames, 1, $i) = CHRW(0x0000) Then DllStructSetData($tVolNames, 1, CHRW(0xFFFD), $i)
    Next

    Return DllStructGetData($tVolNames, 1)
EndFunc   ;==>__WinAPI_GetVolumePathNamesForVolumeName

There are probably other ways to do this but its what I came up with off the top of my head

see here: https://docs.microsoft.com/en-us/windows-server/storage/disk-management/assign-a-mount-point-folder-path-to-a-drive

it'll show you how to add multiple mount point folders to a drive so you can test it

Edited by Bilgus
Cleaned code up a bit
Link to comment
Share on other sites

55 minutes ago, Bilgus said:

;Step through the data turn the nulls into ',' (0x2c00)

That could be a problem if the multi_sz may contain comma(s). Choose another separator character that you should never encounter, like ChrW(0xFFFD) (Not a Character).

And yes those multi_sz are painful and don't even accomodate empty strings if not the first and only one.

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)

Link to comment
Share on other sites

The thought of commas crossed my mind other possibilities include just about any other restricted character

  • < (less than)
  • > (greater than)
  • " (double quote)
  • / (forward slash)
  • | (vertical bar or pipe)
  • ? (question mark)
  • * (asterisk)
Link to comment
Share on other sites

Depending on the use of strings and their semantics, all those characters may occur. The Unicode "invalid character" is essentially guaranteed to be never encountered.

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)

Link to comment
Share on other sites

That's just a technical detail. You can as well walk the string as Word or Uint32 or Short (pick your favorite name) and check for 0x0000 then replace by 0xFFFD (or some other good, highly unlikely choice. Leave the last USHORT as 0x0000 since it will be a good end of string marker.

Having the struct element union-ed as Byte[] is good to retrieve it finally as a whole, but going back to the struct element as returned (WChar) allows to grab it as AutoIt string and avoid binary to string as well.

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)

Link to comment
Share on other sites

_WinAPI_StructToArray will do the heavy lifting of parsing arrays of null terminated unicode strings. :D

Slightly modified version of the previous solution.

#include <WinAPIFiles.au3>
#include <Array.au3>

Global $sMountedPath = "c:\"
Global $sGuid = _WinAPI_GetVolumeNameForVolumeMountPoint($sMountedPath)

ConsoleWrite("Vol Guid: " & $sGuid & @CRLF)
_ArrayDisplay(__WinAPI_GetVolumePathNamesForVolumeName($sGuid))

Func __WinAPI_GetVolumePathNamesForVolumeName($sVolumeGUID)

    Local $tVolNames, $tSize = DllStructCreate("int iSize")
    Local $aPathNames

    ;First call to get size of buffer req"d
    Local $aRet = DllCall("kernel32.dll", "bool", "GetVolumePathNamesForVolumeNameW", _
                          "wstr", $sVolumeGUID, _
                          "ptr", 0, _
                          "dword", 0, _
                          "ptr", DllStructGetPtr($tSize) _
                          )

    $tVolNames = DllStructCreate("byte [" & DllStructGetData($tSize, 1) * 2 & "]") ;Multiply by 2 since TChars are 2 bytes each as far as Autoit is concerned

    ;Second call gets the actual data
    $aRet = DllCall("kernel32.dll", "bool", "GetVolumePathNamesForVolumeNameW", _
                    "wstr", $sVolumeGUID, _
                    "ptr", DllStructGetPtr($tVolNames), _
                    "dword", DllStructGetSize($tVolNames), _
                    "ptr", DllStructGetPtr($tSize) _
                    )
    If @error Or Not $aRet[0] Then Return SetError(@error + 10, @extended, "")

    Return _WinAPI_StructToArray($tVolNames)

EndFunc   ;==>__WinAPI_GetVolumePathNamesForVolumeName

 

Link to comment
Share on other sites

@TheXman Nice! Its pretty much the same idea as above but cleaner since its already made :)

 

Spoiler
Func _WinAPI_StructToArray(ByRef $tStruct, $iItems = 0);Yashied
    Local $iSize = 2 * Floor(DllStructGetSize($tStruct) / 2)
    Local $pStruct = DllStructGetPtr($tStruct)

    If Not $iSize Or Not $pStruct Then Return SetError(1, 0, 0)

    Local $tData, $iLength, $iOffset = 0
    Local $aResult[101] = [0]

    While 1
        $iLength = _WinAPI_StrLen($pStruct + $iOffset)
        If Not $iLength Then
            ExitLoop
        EndIf
        If 2 * (1 + $iLength) + $iOffset > $iSize Then Return SetError(3, 0, 0)
        $tData = DllStructCreate('wchar[' & (1 + $iLength) & ']', $pStruct + $iOffset)
        If @error Then Return SetError(@error + 10, 0, 0)
        __Inc($aResult);;Redims array
        $aResult[$aResult[0]] = DllStructGetData($tData, 1)
        If $aResult[0] = $iItems Then
            ExitLoop
        EndIf
        $iOffset += 2 * (1 + $iLength)
        If $iOffset >= $iSize Then Return SetError(3, 0, 0)
    WEnd
    If Not $aResult[0] Then Return SetError(2, 0, 0)

    __Inc($aResult, -1)
    Return $aResult
EndFunc   ;==>_WinAPI_StructToArray

Func __Inc(ByRef $aData, $iIncrement = 100)
    Select
        Case UBound($aData, $UBOUND_COLUMNS)
            If $iIncrement < 0 Then
                ReDim $aData[$aData[0][0] + 1][UBound($aData, $UBOUND_COLUMNS)]
            Else
                $aData[0][0] += 1
                If $aData[0][0] > UBound($aData) - 1 Then
                    ReDim $aData[$aData[0][0] + $iIncrement][UBound($aData, $UBOUND_COLUMNS)]
                EndIf
            EndIf
        Case UBound($aData, $UBOUND_ROWS)
            If $iIncrement < 0 Then
                ReDim $aData[$aData[0] + 1]
            Else
                $aData[0] += 1
                If $aData[0] > UBound($aData) - 1 Then
                    ReDim $aData[$aData[0] + $iIncrement]
                EndIf
            EndIf
        Case Else
            Return 0
    EndSelect
    Return 1
EndFunc   ;==>__Inc

 

I Included the code from WinAPIMisc.au3 (Autoit 3.3.14)

here Yashied uses _WinAPI_StrLen to walk the string to the next NULL which I imagine is a bit faster

Here it is functionally equivalent

#include <WinAPIFiles.au3>
#Include <Array.au3>
Global $sMountedPath = "c:\" ; Don't forget to end with final '\'
Global $sGuid = _WinAPI_GetVolumeNameForVolumeMountPoint($sMountedPath)

ConsoleWrite("Vol Guid: " & $sGuid & @CRLF)

_ArrayDisplay(__WinAPI_GetVolumePathNamesForVolumeName($sGuid), "Mount Point(s):")

Func __WinAPI_GetVolumePathNamesForVolumeName($sVolumeGUID)

    Local $tVolNames, $tSize = DllStructCreate('int iSize')

    ;First call to get size of buffer req'd
    Local $aRet = DllCall('kernel32.dll', 'bool', 'GetVolumePathNamesForVolumeNameW', _
                          'wstr', $sVolumeGUID,
                          'ptr', 0, _
                          'dword', 0, _
                          'ptr', DllStructGetPtr($tSize) _
                          )

    $tVolNames = DllStructCreate("wchar [" & DllStructGetData($tSize, 1) & "]")

    ;Second call gets the actual data
    $aRet = DllCall('kernel32.dll', 'bool', 'GetVolumePathNamesForVolumeNameW', _
                    'wstr', $sVolumeGUID, _
                    'ptr', DllStructGetPtr($tVolNames), _
                    'dword', DllStructGetSize($tVolNames), _
                    'ptr', DllStructGetPtr($tSize) _
                    )
                    
    If @error Or Not $aRet[0] Then Return SetError(@error + 10, @extended, '')

    ;Step through the data turn the nulls into invalid unicode char (0xFFFD) @jchd
    
    For $i = 1 To (DllStructGetSize($tVolNames)/2) - 2 ;each character occupies (/2) two bytes and we ignore the last (-2) two nulls..
        If DllStructGetData($tVolNames, 1, $i) = CHRW(0x0000) Then DllStructSetData($tVolNames, 1, CHRW(0xFFFD), $i)
    Next

    Return StringSplit(DllStructGetData($tVolNames, 1), CHRW(0xFFFD))
EndFunc   ;==>__WinAPI_GetVolumePathNamesForVolumeName

 

Edited by Bilgus
Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...