SamuelKerschbaumer Posted January 16, 2008 Posted January 16, 2008 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
Siao Posted January 16, 2008 Posted January 16, 2008 (edited) 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 January 16, 2008 by Siao "be smart, drink your wine"
GaryFrost Posted January 16, 2008 Posted January 16, 2008 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 expandcollapse popupFunc _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.
SamuelKerschbaumer Posted January 17, 2008 Author Posted January 17, 2008 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 expandcollapse popupFunc _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.
GaryFrost Posted January 17, 2008 Posted January 17, 2008 ? 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.
Siao Posted January 17, 2008 Posted January 17, 2008 (edited) 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: (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 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 January 17, 2008 by Siao "be smart, drink your wine"
GaryFrost Posted January 17, 2008 Posted January 17, 2008 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: (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 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.
Siao Posted January 17, 2008 Posted January 17, 2008 (edited) 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. 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 January 17, 2008 by Siao "be smart, drink your wine"
GaryFrost Posted January 17, 2008 Posted January 17, 2008 (edited) Because you did what the guy suggested in the first post (despite calling him being wrong too) - not request lparam data with everything else. 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. expandcollapse popupFunc _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 January 17, 2008 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.
SamuelKerschbaumer Posted January 25, 2008 Author Posted January 25, 2008 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
GaryFrost Posted January 25, 2008 Posted January 25, 2008 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.
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now