Jump to content

Memory Leak from DllCall GetWindowText?


MDCT
 Share

Recommended Posts

Hello guys and gals,

I had been searching and reading the forum about memory leak, but couldn't find anything related. So, I'm sorry if this has been answered before.

I have a simple function, but somehow it keeps eating RAM little by little. I will explain it more after the Code.

For $i=1 to 2 step 0
Sleep(150)
$Result=_Check()
;~  ConsoleWrite($Result&@CRLF)
Next

Func _Check()
Dim $Mpos, $hPoint, $FPointWinTitle
$Mpos=MouseGetPos()
$hPoint = DllCall("User32.dll", "hwnd", "WindowFromPoint", "int", $Mpos[0], "int", $Mpos[1])
$FPointWinTitle=DllCall("user32.dll", "int", "GetWindowText", "hwnd", $hPoint[0], "str", "", "int", 32768)
return $FPointWinTitle[2]
EndFunc

As you can see, it mostly does dllcalls on user32 'WindowFromPoint' and 'GetWindowText'.

In the test on my PC, the script would take around 4-16kb if a new window appears. However, slowly it will become less aggressive when no new window appears, but it still eats a bit here and there.

Now, the leak is gone if I take out 'GetWindowText', so the script only calls 'WindowFromPoint'.

Can someone please tell me this is a memory leak or not, and how to make the memory stable?

Thank you.

PS: Oh, almost forgot, I know one way to patch the memory leak using 'EmptyWorkingSet'. But I believe it's not really a fix. So, I'm hoping to find the right fix.

Edited by MDCT
Link to comment
Share on other sites

Personally I would just use _WinAPI_GetWindowText & _WinAPI_WindowFromPoint in WinAPI.au3 as looking at your DLL calls they're not geared up for x64.

Edited by guinness

UDF List:

 
_AdapterConnections()_AlwaysRun()_AppMon()_AppMonEx()_ArrayFilter/_ArrayReduce_BinaryBin()_CheckMsgBox()_CmdLineRaw()_ContextMenu()_ConvertLHWebColor()/_ConvertSHWebColor()_DesktopDimensions()_DisplayPassword()_DotNet_Load()/_DotNet_Unload()_Fibonacci()_FileCompare()_FileCompareContents()_FileNameByHandle()_FilePrefix/SRE()_FindInFile()_GetBackgroundColor()/_SetBackgroundColor()_GetConrolID()_GetCtrlClass()_GetDirectoryFormat()_GetDriveMediaType()_GetFilename()/_GetFilenameExt()_GetHardwareID()_GetIP()_GetIP_Country()_GetOSLanguage()_GetSavedSource()_GetStringSize()_GetSystemPaths()_GetURLImage()_GIFImage()_GoogleWeather()_GUICtrlCreateGroup()_GUICtrlListBox_CreateArray()_GUICtrlListView_CreateArray()_GUICtrlListView_SaveCSV()_GUICtrlListView_SaveHTML()_GUICtrlListView_SaveTxt()_GUICtrlListView_SaveXML()_GUICtrlMenu_Recent()_GUICtrlMenu_SetItemImage()_GUICtrlTreeView_CreateArray()_GUIDisable()_GUIImageList_SetIconFromHandle()_GUIRegisterMsg()_GUISetIcon()_Icon_Clear()/_Icon_Set()_IdleTime()_InetGet()_InetGetGUI()_InetGetProgress()_IPDetails()_IsFileOlder()_IsGUID()_IsHex()_IsPalindrome()_IsRegKey()_IsStringRegExp()_IsSystemDrive()_IsUPX()_IsValidType()_IsWebColor()_Language()_Log()_MicrosoftInternetConnectivity()_MSDNDataType()_PathFull/GetRelative/Split()_PathSplitEx()_PrintFromArray()_ProgressSetMarquee()_ReDim()_RockPaperScissors()/_RockPaperScissorsLizardSpock()_ScrollingCredits_SelfDelete()_SelfRename()_SelfUpdate()_SendTo()_ShellAll()_ShellFile()_ShellFolder()_SingletonHWID()_SingletonPID()_Startup()_StringCompact()_StringIsValid()_StringRegExpMetaCharacters()_StringReplaceWholeWord()_StringStripChars()_Temperature()_TrialPeriod()_UKToUSDate()/_USToUKDate()_WinAPI_Create_CTL_CODE()_WinAPI_CreateGUID()_WMIDateStringToDate()/_DateToWMIDateString()Au3 script parsingAutoIt SearchAutoIt3 PortableAutoIt3WrapperToPragmaAutoItWinGetTitle()/AutoItWinSetTitle()CodingDirToHTML5FileInstallrFileReadLastChars()GeoIP databaseGUI - Only Close ButtonGUI ExamplesGUICtrlDeleteImage()GUICtrlGetBkColor()GUICtrlGetStyle()GUIEventsGUIGetBkColor()Int_Parse() & Int_TryParse()IsISBN()LockFile()Mapping CtrlIDsOOP in AutoItParseHeadersToSciTE()PasswordValidPasteBinPosts Per DayPreExpandProtect GlobalsQueue()Resource UpdateResourcesExSciTE JumpSettings INISHELLHOOKShunting-YardSignature CreatorStack()Stopwatch()StringAddLF()/StringStripLF()StringEOLToCRLF()VSCROLLWM_COPYDATAMore Examples...

Updated: 22/04/2018

Link to comment
Share on other sites

#include <winapi.au3>

For $i=1 to 2 step 0
Sleep(150)
$Result=_Check()
 ConsoleWrite($Result&@CRLF)
Next

Func _Check()
Dim $Struct, $hPoint, $FPointWinTitle
$Struct = DllStructCreate($tagPoint)
DllStructSetData($Struct, "x", MouseGetPos(0))
DllStructSetData($Struct, "y", MouseGetPos(1))
$hPoint = _WinAPI_WindowFromPoint($Struct)
$FPointWinTitle=_WinAPI_GetWindowText($hPoint)
return $FPointWinTitle
EndFunc

Thank you for the suggestion, I tried using '_WinAPI_GetWindowText' and '_WinAPI_WindowFromPoint' from winapi.au3 but it is still taking more and more memory.

Edited by MDCT
Link to comment
Share on other sites

I'm using version 3.3.6.1. The leak doesn't happen if I use only 'WindowFromPoint'. I tried dllcall 'GetClassName', it's also doing the same thing.

It takes RAM mostly when new window is created or new window is active, and sometimes switching between windows, otherwise it is stable. With only 'WindowFromPoint', my script is stable.

Does it happen only on my PC?

Edited by MDCT
Link to comment
Share on other sites

Hi trancexx, thank you for trying to help me.

My system is WIN XP on Pentium-4 3.00GHz with 1Gig of RAM.

I have tried on different PC, but it also keeps taking up RAM as I open new program with window.

The fact that with just 'WindowFromPoint' the script doesn't keep consuming RAM as 'GetWindowText' or 'GetClassName', makes me think perhaps this is a normal behavior of using those dllcall.

As you can see I even use local variables so it will keep removing the past results, thinking that it could help with the RAM.

I hope there's a solution for this "unwanted" behavior, perhaps like running the script on RAM and release all the used RAM after getting the result...this, in my opinion, is better than using 'EmptyWorkingSet'.

Link to comment
Share on other sites

trancexx,

1. I verify RAM usage by using TaskManager, the 'Mem Usage' column.

2. The different PC was running WIN XP SP2 the same with what I'm using.

3. No Autoit was running on different PC, I was just running the compiled version of the above code.

Hmm...as a matter of fact, on the PC that I'm using, I use the 'Self Extracting Archive' version of Autoit, the one that I don't have to install (I try to reduce information being add to the system). However, I doubt this is the case tho, or is it??

Hi MPH,

Thanks for the suggestion. I don't know if this is just my feeling or not..but it seems the leak is getting a bit smaller. Now it doesn't always take RAM immediately after new window shows up. However, it is still taking RAM.

When I started the app, it used 3,996k. After opening new programs (Notepad, Regedit, Firefox, MsPaint, Onscreen Keyboard, control panels applets, etc.) and closing their windows, the script used RAM at 4,248k. And, after I wrote this reply, now it is at 4,280k.

Edit:

Just did a bit of experiment. With just 'WindowFromPoint' the usage is stable at 3,924k.

Now, I remove the 'WindowFromPoint' and set the handle at 0 for the 'GetWindowText' to get the text. It started at 3,952k then after I opened some windows it became 3,964kb and it stays stable at that.

Now, I set the handle for active window (WingetHandle("[Active]")) and the leak is back.

It makes me think, probably, the memory leak that I'm experiencing is caused by the length of the results (texts) that are different from each new window.

Edited by MDCT
Link to comment
Share on other sites

who knows how autoit allocates memory... if its a critical piece of code, i would suggest you allocate it yourself, maybe?

Local $aRet = DllCall("User32.dll","int","GetWindowTextLengthW","hwnd",$hWnd)
Local $nChars = $aRet[0]
Local $bBuf = DllStructCreate("wchar[" & $hChars + 2 & "]") ;Remember null
$aRet = DllCall("User32.dll","int","GetWindowTextLengthW","hwnd",$hWnd,"ptr",DllStructGetPtr($bBuf),"int",$nChars+1)
Local $szWindowText = DllStructGetData($Buf,1)

PS: Oh, almost forgot, I know one way to patch the memory leak using 'EmptyWorkingSet'. But I believe it's not really a fix. So, I'm hoping to find the right fix.

nonono, dont do this Edited by Shaggi

Ever wanted to call functions in another process? ProcessCall UDFConsole stuff: Console UDFC Preprocessor for AutoIt OMG

Link to comment
Share on other sites

Hi Shaggi,

Thank you for the suggestion. Did you mean like this?

For $i=1 to 2 step 0
Sleep(150)
$Result=_Check()
;~  ConsoleWrite($Result&@CRLF)
Next
Func _Check()
Local $Mpos=MouseGetPos()
Local $hPoint = DllCall("User32.dll", "hwnd", "WindowFromPoint", "int", $Mpos[0], "int", $Mpos[1])
Local $aRet = DllCall("User32.dll","int","GetWindowTextLengthW","hwnd",$hPoint[0])
Local $nChars = $aRet[0]
Local $bBuf = DllStructCreate("wchar[" & $nChars + 2 & "]") ;Remember null
$aRet = DllCall("User32.dll","int","GetWindowTextW","hwnd",$hPoint[0],"ptr",DllStructGetPtr($bBuf),"int",$nChars+1)
Local $szWindowText = DllStructGetData($bBuf,1)
return DllStructGetData($bBuf,1)
EndFunc

Unfortunately, it still leaks. It started with 3,830, then after some opening and switching windows it became 4,224kb.

And why using 'EmptyWorkingSet' is bad? If I only use local variables in a function, there should be no un-useful information stored (as at the end of the function those variables are discarded), and maybe the increase in RAM usage is because of the increasing WorkingSet itself...so maybe the real fix is to use 'EmptyWorkingSet', no? If someone could enlighten me, I would very much appreciate it.

Does anyone have any other suggestions?

Link to comment
Share on other sites

Hi Shaggi,

Thank you for the suggestion. Did you mean like this?

For $i=1 to 2 step 0
Sleep(150)
$Result=_Check()
;~  ConsoleWrite($Result&@CRLF)
Next
Func _Check()
Local $Mpos=MouseGetPos()
Local $hPoint = DllCall("User32.dll", "hwnd", "WindowFromPoint", "int", $Mpos[0], "int", $Mpos[1])
Local $aRet = DllCall("User32.dll","int","GetWindowTextLengthW","hwnd",$hPoint[0])
Local $nChars = $aRet[0]
Local $bBuf = DllStructCreate("wchar[" & $nChars + 2 & "]") ;Remember null
$aRet = DllCall("User32.dll","int","GetWindowTextW","hwnd",$hPoint[0],"ptr",DllStructGetPtr($bBuf),"int",$nChars+1)
Local $szWindowText = DllStructGetData($bBuf,1)
return DllStructGetData($bBuf,1)
EndFunc

Unfortunately, it still leaks. It started with 3,830, then after some opening and switching windows it became 4,224kb.

And why using 'EmptyWorkingSet' is bad? If I only use local variables in a function, there should be no un-useful information stored (as at the end of the function those variables are discarded), and maybe the increase in RAM usage is because of the increasing WorkingSet itself...so maybe the real fix is to use 'EmptyWorkingSet', no? If someone could enlighten me, I would very much appreciate it.

Does anyone have any other suggestions?

Its a bad practice because you are abstracting from the real problem, your program leaking. Btw any reason you dont use WinGetText()?

I tested the code and i experience no leaks, whatsoever. I dont think it's a problem with the code but maybe the enviroment you're using it in... Remember other processes in your system can potentially create memory leaks in your program. Antivirus and antimalware programs also liberally inject themselves into other processes.

Lastly, when you say memoryleak, would the code potentially leak 100 if not 1000 of megabytes? If we are talking small jumps < 10 mb, this is not the problem.

Ever wanted to call functions in another process? ProcessCall UDFConsole stuff: Console UDFC Preprocessor for AutoIt OMG

Link to comment
Share on other sites

Its a bad practice because you are abstracting from the real problem, your program leaking. Btw any reason you dont use WinGetText()?

Because WinGetText() is slower than dllcall. On my PC 'GetWindowText' it is around 20x faster. And I want to put as many sleep as possible to reduce CPU while maintaining the script's resposiveness.

I tested the code and i experience no leaks, whatsoever. I dont think it's a problem with the code but maybe the enviroment you're using it in... Remember other processes in your system can potentially create memory leaks in your program. Antivirus and antimalware programs also liberally inject themselves into other processes.

Lastly, when you say memoryleak, would the code potentially leak 100 if not 1000 of megabytes? If we are talking small jumps < 10 mb, this is not the problem.

Ahhh...now I see. Maybe I was being too bitchy about the RAM consumption, because I was comparing it with 'WindowFromPoint'. With only 'WindowFromPoint' the script doesn't take any additional RAM at all. With 'GetWindowText' it takes mostly 4,000-16,000 byte almost constantly each time I open a new window, so it's still a long way to reach 10mb..maybe in days it will reach that.

This is not a leak then, so 'EmptyWorkingSet' after some times is ok I reckon, just to keep the Mem usage from increasing in time. And for my own personal reference, I will keep in mind that environment could cause my script to leak.

Thank you Shaggi for your information and help.

Link to comment
Share on other sites

Because WinGetText() is slower than dllcall. On my PC 'GetWindowText' it is around 20x faster. And I want to put as many sleep as possible to reduce CPU while maintaining the script's resposiveness.

Ahhh...now I see. Maybe I was being too bitchy about the RAM consumption, because I was comparing it with 'WindowFromPoint'. With only 'WindowFromPoint' the script doesn't take any additional RAM at all. With 'GetWindowText' it takes mostly 4,000-16,000 byte almost constantly each time I open a new window, so it's still a long way to reach 10mb..maybe in days it will reach that.

This is not a leak then, so 'EmptyWorkingSet' after some times is ok I reckon, just to keep the Mem usage from increasing in time. And for my own personal reference, I will keep in mind that environment could cause my script to leak.

Thank you Shaggi for your information and help.

Its still weird though, if this is the only code you use... Anyway, if speed is so crucial that the inbuilt functions in autoit is not quick enough, you might want to consider another language? :D heres the example in c++, not so much different - and a lot quicker:

#include <Windows.h>
#include <string>
#include <iostream>

std::string _Check();

int main(int argc, char ** argv) {
  std::string result;
  while (true)
  {
    result = _Check();
    std::cout << result;
    Sleep(150);
  }
  return 0;
}

std::string _Check() {
  HWND hWnd;
  POINT mPos;
  GetMousePos(&mPos);
  hWnd = WindowFromPoint(mPos);
  int nLen = GetWindowTextLengthA(hWNd);
  char * txtbuffer = new char[nLen + 1];
  GetWindowTextA(hWnd,txtbuffer,nLen+1);
  std::string ret(txtbuffer);
  delete [] txtbuffer;
  return ret;
}
Edited by Shaggi

Ever wanted to call functions in another process? ProcessCall UDFConsole stuff: Console UDFC Preprocessor for AutoIt OMG

Link to comment
Share on other sites

It is very weird, Shaggi. I even tried on different AutoIt script that has nothing to do with dllcall 'GetWindowText', and it was also taking 4kb-16kb mostly when a new window is activated. It looks like the Autoit's engine is dependent on windows creations or something.

Below is the sample script that I use:

#include 
#include 
#include 
Dim $item[6]
GUICreate("listview items", 300, 200, -1, -1, -1, $WS_EX_ACCEPTFILES)
$listview = GUICtrlCreateListView("col1     |col2      |col3 ", 10, 10, 200, 150, -1, $LVS_EX_CHECKBOXES)
$button = GUICtrlCreateButton("Get What Names Are Checked?", 75, 170, 170, 20)
$item[1] = GUICtrlCreateListViewItem("item1|col22|col23", $listview)
$item[2] = GUICtrlCreateListViewItem("item2|col12|col13", $listview)
$item[3] = GUICtrlCreateListViewItem("item3|c3332|col33", $listview)
$item[4] = GUICtrlCreateListViewItem("item4|44444|col33", $listview)
$item[5] = GUICtrlCreateListViewItem("item5|5555|col33", $listview)
GUISetState()
Do
$msg = GUIGetMsg()
Select
  Case $msg = $button
   For $x = 1 To 5
    If _GUICtrlListView_GetItemChecked($listview, $x - 1) Then
     MsgBox(0, "listview item", _GUICtrlListView_GetItemTextString($listview, $x - 1) & "   " & @CRLF & "Line Checked = " & $x, 2)
    EndIf
   Next
  Case $msg = $listview
   MsgBox(0, "listview", "clicked=" & GUICtrlGetState($listview), 2)
EndSelect
Until $msg = $GUI_EVENT_CLOSE
As you can see, it's just a normal listview script.

I ran the script from SciTe, switched between windows, opened new windows....and if you look at the TaskMan, you will see the RAM usage is increasing little by little eventhough the script has nothing to do with window switchings or creations.

I was thinking to create an app that will be run 24/7 on my PC (mostly restart every 3 weeks) so I want to create applications that are efficient in RAM, CPU and speed wise in order for the app not to slow things down. I have spent hours reading the forum and doing many experiments to find the best commands or functions for my apps.

Constant RAM eating on most Autoit scripts eventho small makes me a bit dissapointed.

I guess I should use Autoit to create scripts that are not to be used 24/7 as Autoit scripts tend to consume additional memory over time.

Also, I just read from the net: Memory leaks are a common error in programming, especially when using languages that have no built-in automatic garbage collection, such as C and C++.

So, yeah, I guess C++ is the way to go for my 24/7 app, Shaggi. The C++ example doesn't look that different but it looks quite confusing for now. Thank you so much for your help and suggestions, Shaggi. :D

Take care.

PS: On second thought..maybe it's a good idea to ask Autoit gods or goddesses about this behavior first.

Edited by MDCT
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...