Jump to content
Sign in to follow this  
SamuelKerschbaumer

BUG When Getting Text of Tab Items

Recommended Posts

SamuelKerschbaumer

Hello.

I am trying to get the text of tab items (SysTabControl32) of a external application.

The function to use is _GUICtrlTab_GetItemText().

This function seems to have some problems with some Tabcontrols.

To reproduce:

1. Open Tools/Folder Options in explorer

2. Run this script:

#include <GuiTab.au3>

$tab = ControlGetHandle("Folder Options", "", "[Class:SysTabControl32]")

ConsoleWrite(_GUICtrlTab_GetItemText($tab, 0))

The Script should output "General", instead no text is shown for me.

AutoIT Version: 3.2.10.0

Windows Version: Windows XP SP2

Workaround/Bugfix:

It seems to have to do something with the Mask field in the TCITEM structure.

When the line 543 in GuiTab.au3 'DllStructSetData($item, "Mask", $TCIF_ALLDATA)' is changed to 'DllStructSetData($item, "Mask", 0x13)' the thing works for me.

0x13 corresponds to TCIF_IMAGE | TCIF_STATE | TCIF_TEXT, it is $TCIF_ALLDATA without the bit TCIF_PARAM.

Strangely enough, this problem occurs only in some applications.

Any hints why it is happening? Any Ideas for Bugfixes? A possible soulution would be to create a _GUICtrlTab_GetItem which takes the mask as parameter, the _GUICtrlTab_GetItem could call the function with $TCIF_ALLDATA and _GUICtrlTab_GetItemText calls it only with the bitmask for getting the text.

Regards,

Samuel

Share this post


Link to post
Share on other sites
Siao

Good first post.

I'd say this happens because of this piece in _GUICtrlTab_GetItem function:

$iItem = DllStructGetSize($tItem)
            $pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
            $pText = $pMemory + $iItem

So if the app-defined data happens to be more than 4 bytes which is possible in some cases according to MS, right now it overwrites text buffer.

In your workaround, you don't request lparam, so everything's fine.

Or maybe it's simply that requesting "all data" isn't that good of an idea.

Edited by Siao

"be smart, drink your wine"

Share this post


Link to post
Share on other sites
GaryFrost

Workaround/Bugfix:

It seems to have to do something with the Mask field in the TCITEM structure.

Regards,

Samuel

Wrong on bug, right on structure. see below.

Good first post.

I'd say this happens because of this piece in _GUICtrlTab_GetItem function:

$iItem = DllStructGetSize($tItem)
            $pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
            $pText = $pMemory + $iItem

So if the app-defined data happens to be more than 4 bytes which is possible in some cases according to MS, right now it overwrites text buffer.

In your workaround, you don't request lparam, so everything's fine.

Or maybe it's simply that requesting "all data" isn't that good of an idea.

Wrong you need that for the buffer.

lParam

Application-defined data associated with the tab control item. If more or less than 4 bytes of application-defined data exist per tab, an application must define a structure and use it instead of the TCITEM structure. The first member of the application-defined structure must be a TCITEMHEADER structure.

Good ole MS decided to use a structure other than the standard TCITEM structure.

Here is how you would have to modify the functions to read the non-standard TCITEM

Func _GUICtrlTab_GetItem($hWnd, $iIndex)
    Local $pBuffer, $tBuffer, $iItem, $pItem, $tItem, $pMemory, $tMemMap, $pText, $aItem[4], $iResult, $iParam = 0
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    
    $tBuffer = DllStructCreate("char Text[4096]")
    $pBuffer = DllStructGetPtr($tBuffer)
    $tItem = DllStructCreate($tagTCITEM)
    $pItem = DllStructGetPtr($tItem)
    DllStructSetData($tItem, "Mask", BitOR($TCIF_IMAGE, $TCIF_STATE, $TCIF_TEXT))
;~  DllStructSetData($tItem, "Mask", $TCIF_ALLDATA)
    DllStructSetData($tItem, "TextMax", 4096)
    $iItem = DllStructGetSize($tItem)
    $pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
    $pText = $pMemory + $iItem
    DllStructSetData($tItem, "Text", $pText)
    _MemWrite($tMemMap, $pItem, $pMemory, $iItem)
    $iResult = _SendMessage($hWnd, $TCM_GETITEM, $iIndex, $pMemory)
    _MemRead($tMemMap, $pMemory, $pItem, $iItem)
    _MemRead($tMemMap, $pText, $pBuffer, 4096)
    _MemFree($tMemMap)
    $iParam = _GUICtrlTab_GetItemParam($hWnd, $iIndex)
    $aItem[0] = DllStructGetData($tItem, "State")
    $aItem[1] = DllStructGetData($tBuffer, "Text")
    $aItem[2] = DllStructGetData($tItem, "Image")
;~  $aItem[3] = DllStructGetData($tItem, "Param")
    $aItem[3] = $iParam
    Return SetError($iResult <> 0, 0, $aItem)
EndFunc   ;==>_GUICtrlTab_GetItemoÝ÷ Ù©Ýjëh×6Func _GUICtrlTab_GetItemParam($hWnd, $iIndex)
    If $Debug_TAB Then _GUICtrlTab_ValidateClassName($hWnd)
    Local $pBuffer, $tBuffer, $iItem, $pItem, $tItem, $pMemory, $tMemMap, $pText, $aItem[4], $iResult
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    
    $tBuffer = DllStructCreate("char Text[4096]")
    $pBuffer = DllStructGetPtr($tBuffer)
    $tItem = DllStructCreate($tagTCITEM)
    $pItem = DllStructGetPtr($tItem)
    DllStructSetData($tItem, "Mask", $TCIF_PARAM)
    DllStructSetData($tItem, "TextMax", 4096)
    $iItem = DllStructGetSize($tItem)
    $pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
    $pText = $pMemory + $iItem
    DllStructSetData($tItem, "Text", $pText)
    _MemWrite($tMemMap, $pItem, $pMemory, $iItem)
    $iResult = _SendMessage($hWnd, $TCM_GETITEM, $iIndex, $pMemory, 0, "wparam", "ptr")
    _MemRead($tMemMap, $pMemory, $pItem, $iItem)
    _MemRead($tMemMap, $pText, $pBuffer, 4096)
    _MemFree($tMemMap)
    Return SetError($iResult <> 0, 0, DllStructGetData($tItem, "Param"))
EndFunc   ;==>_GUICtrlTab_GetItemParam

Not sure I want to change the functions or not, I'll mull it over a bit.


SciTE for AutoItDirections for Submitting Standard UDFs

 

Don't argue with an idiot; people watching may not be able to tell the difference.

 

Share this post


Link to post
Share on other sites
SamuelKerschbaumer

Wrong you need that for the buffer.

? I don't understand.

Good ole MS decided to use a structure other than the standard TCITEM structure.

Which is perfectly legal according to Windows API docs.

Here is how you would have to modify the functions to read the non-standard TCITEM

Func _GUICtrlTab_GetItem($hWnd, $iIndex)
    Local $pBuffer, $tBuffer, $iItem, $pItem, $tItem, $pMemory, $tMemMap, $pText, $aItem[4], $iResult, $iParam = 0
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    
    $tBuffer = DllStructCreate("char Text[4096]")
    $pBuffer = DllStructGetPtr($tBuffer)
    $tItem = DllStructCreate($tagTCITEM)
    $pItem = DllStructGetPtr($tItem)
    DllStructSetData($tItem, "Mask", BitOR($TCIF_IMAGE, $TCIF_STATE, $TCIF_TEXT))
;~  DllStructSetData($tItem, "Mask", $TCIF_ALLDATA)
    DllStructSetData($tItem, "TextMax", 4096)
    $iItem = DllStructGetSize($tItem)
    $pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
    $pText = $pMemory + $iItem
    DllStructSetData($tItem, "Text", $pText)
    _MemWrite($tMemMap, $pItem, $pMemory, $iItem)
    $iResult = _SendMessage($hWnd, $TCM_GETITEM, $iIndex, $pMemory)
    _MemRead($tMemMap, $pMemory, $pItem, $iItem)
    _MemRead($tMemMap, $pText, $pBuffer, 4096)
    _MemFree($tMemMap)
    $iParam = _GUICtrlTab_GetItemParam($hWnd, $iIndex)
    $aItem[0] = DllStructGetData($tItem, "State")
    $aItem[1] = DllStructGetData($tBuffer, "Text")
    $aItem[2] = DllStructGetData($tItem, "Image")
;~  $aItem[3] = DllStructGetData($tItem, "Param")
    $aItem[3] = $iParam
    Return SetError($iResult <> 0, 0, $aItem)
EndFunc   ;==>_GUICtrlTab_GetItemoÝ÷ Ù©Ýjëh×6Func _GUICtrlTab_GetItemParam($hWnd, $iIndex)
    If $Debug_TAB Then _GUICtrlTab_ValidateClassName($hWnd)
    Local $pBuffer, $tBuffer, $iItem, $pItem, $tItem, $pMemory, $tMemMap, $pText, $aItem[4], $iResult
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    
    $tBuffer = DllStructCreate("char Text[4096]")
    $pBuffer = DllStructGetPtr($tBuffer)
    $tItem = DllStructCreate($tagTCITEM)
    $pItem = DllStructGetPtr($tItem)
    DllStructSetData($tItem, "Mask", $TCIF_PARAM)
    DllStructSetData($tItem, "TextMax", 4096)
    $iItem = DllStructGetSize($tItem)
    $pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
    $pText = $pMemory + $iItem
    DllStructSetData($tItem, "Text", $pText)
    _MemWrite($tMemMap, $pItem, $pMemory, $iItem)
    $iResult = _SendMessage($hWnd, $TCM_GETITEM, $iIndex, $pMemory, 0, "wparam", "ptr")
    _MemRead($tMemMap, $pMemory, $pItem, $iItem)
    _MemRead($tMemMap, $pText, $pBuffer, 4096)
    _MemFree($tMemMap)
    Return SetError($iResult <> 0, 0, DllStructGetData($tItem, "Param"))
EndFunc   ;==>_GUICtrlTab_GetItemParam
I think this code has some problems as well: It assumes that the custom TCITEM strcuture fits into the 4K block, otherwise it would cause a buffer overflow.

Another issue:

The documentation says:

If the TCIF_TEXT flag is set in the mask member of the TCITEM structure, the control may change the pszText member of the structure to point to the new text instead of filling the buffer with the requested text.

I think we should handle this as well.

Not sure I want to change the functions or not, I'll mull it over a bit.

It would be great if a fixed soulution would be included in the next AutoIt Version. I do not really care about the lParam item (I think not many people will be interested in it) but it would be great if the caption could be read out correctly.

Share this post


Link to post
Share on other sites
GaryFrost

? I don't understand.

Which is perfectly legal according to Windows API docs.

I think this code has some problems as well: It assumes that the custom TCITEM strcuture fits into the 4K block, otherwise it would cause a buffer overflow.

Another issue:

The documentation says:

If the TCIF_TEXT flag is set in the mask member of the TCITEM structure, the control may change the pszText member of the structure to point to the new text instead of filling the buffer with the requested text.

I think we should handle this as well.

It would be great if a fixed soulution would be included in the next AutoIt Version. I do not really care about the lParam item (I think not many people will be interested in it) but it would be great if the caption could be read out correctly.

Then come up with a fix that works in all situations, and show that it works.


SciTE for AutoItDirections for Submitting Standard UDFs

 

Don't argue with an idiot; people watching may not be able to tell the difference.

 

Share this post


Link to post
Share on other sites
Siao

Wrong you need that for the buffer.

That's very nice of you to dismiss me like that, but where am I wrong exactly?

That bit of code I quoted, allocates text buffer in memory right after TCITEM structure. And if lparam member happens to exceed 4 bytes designated to it, guess WHERE the rest will be written?

With the following example, which brings text of "Hardware" tab of system properties,

#Include <GuiTab.au3>

$title = "System Properties"
Run('rundll32.exe shell32.dll,Control_RunDLL sysdm.cpl')
WinWait($title, '', 10)
$hwnd = ControlGetHandle($title,'','[Class:SysTabControl32]')
$s = _GUICtrlTab_GetItemText($hWnd, 2)

ConsoleWrite($hwnd & @CRLF & $s & @CRLF)
the following memory view: Posted Image (TCITEM struct starts at 0xB40000)

If you allocate text buffer somewhere else (leaving extra bytes between TCITEM and text, or allocating text before TCITEM) it doesn't get overwritten and function returns text correctly. Though that still leaves the matter of only 4 bytes of lparam returned in any case. Not sure how to actually solve that without knowing how many bytes lparam is gonna be, and MS doesn't tell that. But perhaps that isn't a big issue, considering it has been this way all along and no one complained :D

Good ole MS decided to use a structure other than the standard TCITEM structure.

And that TCITEMHEADER is basically identical to the TCITEM structure sans lparam, just State and StateMask members are not used (two reserved dwords, which is the same as pre-IE3 TCITEM). Edited by Siao

"be smart, drink your wine"

Share this post


Link to post
Share on other sites
GaryFrost

That's very nice of you to dismiss me like that, but where am I wrong exactly?

That bit of code I quoted, allocates text buffer in memory right after TCITEM structure. And if lparam member happens to exceed 4 bytes designated to it, guess WHERE the rest will be written?

With the following example, which brings text of "Hardware" tab of system properties,

#Include <GuiTab.au3>

$title = "System Properties"
Run('rundll32.exe shell32.dll,Control_RunDLL sysdm.cpl')
WinWait($title, '', 10)
$hwnd = ControlGetHandle($title,'','[Class:SysTabControl32]')
$s = _GUICtrlTab_GetItemText($hWnd, 2)

ConsoleWrite($hwnd & @CRLF & $s & @CRLF)
the following memory view: Posted Image (TCITEM struct starts at 0xB40000)

If you allocate text buffer somewhere else (leaving extra bytes between TCITEM and text, or allocating text before TCITEM) it doesn't get overwritten and function returns text correctly. Though that still leaves the matter of only 4 bytes of lparam returned in any case. Not sure how to actually solve that without knowing how many bytes lparam is gonna be, and MS doesn't tell that. But perhaps that isn't a big issue, considering it has been this way all along and no one complained :D

And that TCITEMHEADER is basically identical to the TCITEM structure sans lparam, just State and StateMask members are not used (two reserved dwords, which is the same as pre-IE3 TCITEM).

The above code I posted which needs to be cleaned up does work, no matter how many bytes is used for lparam.

SciTE for AutoItDirections for Submitting Standard UDFs

 

Don't argue with an idiot; people watching may not be able to tell the difference.

 

Share this post


Link to post
Share on other sites
Siao

The above code I posted which needs to be cleaned up does work, no matter how many bytes is used for lparam.

Because you did what the guy suggested in the first post (despite calling him being wrong too) - not request lparam data with everything else. :D

And still 4 bytes of lparam are returned, no matter how many are there.

So I don't know what exactly you wanted to say with your reply.

It works? Congrats.

In original _GUICtrlTab_GetItem, having

$pMemory = _MemInit($hWnd, 8192, $tMemMap)
    $pText = $pMemory + 4096

instead of

$pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
    $pText = $pMemory + $iItem

would work the same too, with $TCIF_ALLDATA as mask, and without having extra function call to get just lparam data.

Edited by Siao

"be smart, drink your wine"

Share this post


Link to post
Share on other sites
GaryFrost

Because you did what the guy suggested in the first post (despite calling him being wrong too) - not request lparam data with everything else. :D

And still 4 bytes of lparam are returned, no matter how many are there.

So I don't know what exactly you wanted to say with your reply.

It works? Congrats.

In original _GUICtrlTab_GetItem, having

$pMemory = _MemInit($hWnd, 8192, $tMemMap)
    $pText = $pMemory + 4096

instead of

$pMemory = _MemInit($hWnd, $iItem + 4096, $tMemMap)
    $pText = $pMemory + $iItem

would work the same too, with $TCIF_ALLDATA as mask, and without having extra function call to get just lparam data.

I concede, your way is better. Still not a bug but a design flaw on the Author's part.

On another note, I did find a bug in that function and the _GUICtrlTab_SetItem. Both were missing setting the State Mask, therefore _GUICtrlTab_GetState and _GUICtrlTab_SetState were not working and the [0] of _GUICtrlTab_SetItem was not populated.

Func _GUICtrlTab_GetItem($hWnd, $iIndex)
    If $Debug_TAB Then _GUICtrlTab_ValidateClassName($hWnd)
    Local $pBuffer, $tBuffer, $iItem, $pItem, $tItem, $pMemory, $tMemMap, $pText, $aItem[4], $iResult
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    
    $tBuffer = DllStructCreate("char Text[4096]")
    $pBuffer = DllStructGetPtr($tBuffer)
    $tItem = DllStructCreate($tagTCITEM)
    $pItem = DllStructGetPtr($tItem)
    DllStructSetData($tItem, "Mask", $TCIF_ALLDATA)
    DllStructSetData($tItem, "TextMax", 4096)
    DllStructSetData($tItem, "StateMask", BitOR($TCIS_HIGHLIGHTED, $TCIS_BUTTONPRESSED))
    $iItem = DllStructGetSize($tItem)
    $pMemory = _MemInit($hWnd, $iItem + 8192, $tMemMap)
    $pText = $pMemory + 4096
    DllStructSetData($tItem, "Text", $pText)
    _MemWrite($tMemMap, $pItem, $pMemory, $iItem)
    $iResult = _SendMessage($hWnd, $TCM_GETITEM, $iIndex, $pMemory)
    _MemRead($tMemMap, $pMemory, $pItem, $iItem)
    _MemRead($tMemMap, $pText, $pBuffer, 4096)
    _MemFree($tMemMap)
    $aItem[0] = DllStructGetData($tItem, "State")
    $aItem[1] = DllStructGetData($tBuffer, "Text")
    $aItem[2] = DllStructGetData($tItem, "Image")
    $aItem[3] = DllStructGetData($tItem, "Param")
    Return SetError($iResult <> 0, 0, $aItem)
EndFunc   ;==>_GUICtrlTab_GetItemoÝ÷ ØʯzØ^騯+-¹÷ð®à¢{azÇ­jëh×6Func _GUICtrlTab_SetItem($hWnd, $iIndex, $sText = -1, $iState = -1, $iImage = -1, $iParam = -1)
    If $Debug_TAB Then _GUICtrlTab_ValidateClassName($hWnd)
    If Not IsHWnd($hWnd) Then $hWnd = GUICtrlGetHandle($hWnd)
    Local $iBuffer, $pBuffer, $tBuffer, $iMask = 0, $iItem, $pItem, $tItem, $pMemory, $tMemMap, $pText, $iResult

    $tItem = DllStructCreate($tagTCITEM)
    $pItem = DllStructGetPtr($tItem)
    If IsString($sText) Then
        $iBuffer = StringLen($sText) + 1
        $tBuffer = DllStructCreate("char Text[" & $iBuffer & "]")
        $pBuffer = DllStructGetPtr($tBuffer)
        DllStructSetData($tBuffer, "Text", $sText)
        DllStructSetData($tItem, "Text", $pBuffer)
        $iMask = $TCIF_TEXT
    EndIf
    If $iState <> -1 Then
        DllStructSetData($tItem, "State", $iState)
        DllStructSetData($tItem, "StateMask", $iState)
        $iMask = BitOR($iMask, $TCIF_STATE)
    EndIf
    If $iImage <> -1 Then
        DllStructSetData($tItem, "Image", $iImage)
        $iMask = BitOR($iMask, $TCIF_IMAGE)
    EndIf
    If $iParam <> -1 Then
        DllStructSetData($tItem, "Param", $iParam)
        $iMask = BitOR($iMask, $TCIF_PARAM)
    EndIf
    DllStructSetData($tItem, "Mask", $iMask)
    $iItem = DllStructGetSize($tItem)
    $pMemory = _MemInit($hWnd, $iItem + 8192, $tMemMap)
    $pText = $pMemory + 4096
    DllStructSetData($tItem, "Text", $pText)
    _MemWrite($tMemMap, $pItem, $pMemory, $iItem)
    If IsString($sText) Then _MemWrite($tMemMap, $pBuffer, $pText, $iBuffer)
    $iResult = _SendMessage($hWnd, $TCM_SETITEM, $iIndex, $pMemory) <> 0
    _MemFree($tMemMap)
    Return $iResult
EndFunc   ;==>_GUICtrlTab_SetItem
Edited by GaryFrost

SciTE for AutoItDirections for Submitting Standard UDFs

 

Don't argue with an idiot; people watching may not be able to tell the difference.

 

Share this post


Link to post
Share on other sites
SamuelKerschbaumer

Whats the current status of this thing?

Is it getting an official bug?

Any plans for resolution?

My suggestion would be:

- Create an new function which gets the mask passed.

- Let the old function call the new function, but write warning into the documentation about the security issues.

- Any function in GuiTab.au3 which only needs the text from the tabs should call the new function, with the appropriate mask.

Regarding the issue with the changing text pointer:

Could we write instead of:

$aItem[1] = DllStructGetData($tBuffer, "Text")

the following lines:

$tBuffer = DllStructCreate("char Text[4096]", DllStructGetData($tItem, "Text"))
$aItem[1] = DllStructGetData($tBuffer, "Text")

What do you think?

Regards,

Samuel

Share this post


Link to post
Share on other sites
GaryFrost

Whats the current status of this thing?

Is it getting an official bug?

Any plans for resolution?

My suggestion would be:

- Create an new function which gets the mask passed.

- Let the old function call the new function, but write warning into the documentation about the security issues.

- Any function in GuiTab.au3 which only needs the text from the tabs should call the new function, with the appropriate mask.

Regarding the issue with the changing text pointer:

Could we write instead of:

$aItem[1] = DllStructGetData($tBuffer, "Text")

the following lines:

$tBuffer = DllStructCreate("char Text[4096]", DllStructGetData($tItem, "Text"))
$aItem[1] = DllStructGetData($tBuffer, "Text")

What do you think?

Regards,

Samuel

Wait for the beta release, anytime now.

SciTE for AutoItDirections for Submitting Standard UDFs

 

Don't argue with an idiot; people watching may not be able to tell the difference.

 

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
Sign in to follow this  

×