AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

Get help with using AutoHotkey and its commands and hotkeys
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

29 Jul 2016, 15:42

I'm having an issue with AutoComplete() (from nepter, using WinAPI's iAutoComplete2, here) crashing my script randomly. Sometimes it will work fine (and when it does its awesome), sometimes it will crash evident by the window and process just disappearing, and sometimes it will crash with a windows "Autohotkey XXX has stopped working" dialog (all ahk versions, see attached errors)

On the flip side, SHAutoComplete (which contains the list of files/url the same as windows Run dialog or any open/save dialog) appears to work fine, and ultimately has the same UI features (autosuggest listbox sizeable flyout)

The function is pretty short/simple to read (register callbacks?), but I can't figure out how to tell what kind of a crash is happening or what to do about it, so any help greatly appreciated, thanks!
- Joel

Code: Select all

/*	Autocompletion
	
	Function	:	Autocomplete(hwnd , action , p1=0 , p2=0)
	Parameter
		hwnd	:	hwnd of edit control
		action	:	init , enable , disable , release , option
	
	Usage:
	
	1.	Initializes the autocomplete object.
	Function	:	Autocomplete(hwnd , "init" , txt , delim)
	Parameter
		txt		:	Autocomplete list
		delim	:	delimiter seperate each item
	
	2. 	Enables autocompletion.
	Function	:	Autocomplete(hwnd , "enable" , 0 , 0)
		
	3.	Disables autocompletion.
	Function	:	Autocomplete(hwnd , "disable" , 0 , 0)
		
	4.	Release autocompletion.
	Function	:	Autocomplete(hwnd , "release" , 0 , 0)
		
	5.	Sets the current autocomplete options.
	Function	:	Autocomplete(hwnd , "option" , mode , 0)
	Parameter
		mode	:	options seperated by space
		AUTOSUGGEST			:	Enable the autosuggest drop-down list.
		AUTOAPPEND			:	Enable autoappend.
		SEARCH				:	Add a search item to the list of completed strings. When the user selects this item, it launches a search engine.
		FILTERPREFIXES		:	Do not match common prefixes, such as "www." or "http://".
		USETAB				:	Use the TAB key to select an item from the drop-down list.
		UPDOWNKEYDROPSLIST	:	Use the UP ARROW and DOWN ARROW keys to display the autosuggest drop-down list.
		RTLREADING			:	read right-to-left (RTL). 
		WORD_FILTER			:	If set, the autocompleted suggestion is treated as a phrase for search purposes.
		NOPREFIXFILTERING	:	Disable prefix filtering when displaying the autosuggest dropdown. Always display all suggestions.
*/


AutoComplete(self,celt,rgelt,pceltFetched){
    static es:=[]
    if (celt="init"){ ; Initializes the autocomplete object.
        sList:=[]
        loop,parse,rgelt,%pceltFetched%,%A_Space%%A_Tab%
            sList[A_Index]:=A_LoopField
        obj:=[],obj.List:=sList,obj.CurrentElement:=1,obj.hwnd:=self
        obj.SetCapacity("EnumString",A_PtrSize*8)
        pes:=obj.GetAddress("EnumString")
        ,NumPut(pes+A_PtrSize,pes+0)
        ,NumPut(RegisterCallback("_EnumString_QueryInterface","F"),pes+A_PtrSize*1)
        ,NumPut(RegisterCallback("_EnumString_AddRef","F"),pes+A_PtrSize*2)
        ,NumPut(RegisterCallback("_EnumString_Release","F"),pes+A_PtrSize*3)
        ,NumPut(RegisterCallback(A_ThisFunc,"F"),pes+A_PtrSize*4)
        ,NumPut(RegisterCallback("_EnumString_Skip","F"),pes+A_PtrSize*5)
        ,NumPut(RegisterCallback("_EnumString_Reset","F"),pes+A_PtrSize*6)
        ,NumPut(RegisterCallback("_EnumString_Clone","F"),pes+A_PtrSize*7)
        pac2:=ComObjCreate("{00BB2763-6A77-11D0-A535-00C04FD7D062}","{EAC04BC0-3791-11d2-BB95-0060977B464C}")    ; IAutoComplete2
        obj.pac:=pac2
        DllCall(NumGet(NumGet(pac2+0)+3*A_PtrSize),"ptr",pac2,"ptr",self,"ptr",pes,"ptr",0,"ptr",0,"uint")
        es[pes]:=obj
        return 0
    }else if (celt="enable"){ ; Enables autocompletion.
        for k,v in es
        {
            if (v.hwnd=self)
                return DllCall(NumGet(NumGet(v.pac+0)+4*A_PtrSize),"ptr",v.pac,"int",1,"uint")
        }
        return
    }else if (celt="disable"){ ; Disables autocompletion.
        for k,v in es
        {
            if (v.hwnd=self)
                return DllCall(NumGet(NumGet(v.pac+0)+4*A_PtrSize),"ptr",v.pac,"int",0,"uint")
        }
        return
    }else if (celt="release"){ ; Release autocompletion.
        for k,v in es
        {
            if (v.hwnd=self)
                ObjRelease(v.pac),es.remove(k)
        }
        return
    }else if (celt="option"){ ; Sets the current autocomplete options.
        if rgelt is Integer
        {
            if rgelt<0x200
                option:=rgelt
            else return
        }else{
            mode:={AUTOSUGGEST:1    ; Enable the autosuggest drop-down list.
                ,AUTOAPPEND:2        ; Enable autoappend.
                ,SEARCH:4            ; Add a search item to the list of completed strings. When the user selects this item, it launches a search engine.
                ,FILTERPREFIXES:8    ; Do not match common prefixes, such as "www." or "http://".
                ,USETAB:0x10        ; Use the TAB key to select an item from the drop-down list.
                ,UPDOWNKEYDROPSLIST:0x20    ; Use the UP ARROW and DOWN ARROW keys to display the autosuggest drop-down list.
                ,RTLREADING:0x40    ; read right-to-left (RTL).
                ,WORD_FILTER:0x80    ; If set, the autocompleted suggestion is treated as a phrase for search purposes. The suggestion, Microsoft Office, would be treated as "Microsoft Office" (where both Microsoft AND Office must appear in the search results).
                ,NOPREFIXFILTERING:0x100}    ; Disable prefix filtering when displaying the autosuggest dropdown. Always display all suggestions.
            option:=0
            loop,parse,rgelt,%A_Space%
                if mode[A_LoopField]
                    option|=mode[A_LoopField]
        }
        for k,v in es
        {
            if (v.hwnd=self)
                return DllCall(NumGet(NumGet(v.pac+0)+5*A_PtrSize),"ptr",v.pac,"uint",option,"uint") ; IAutoComplete2::SetOptions
        }
        return
    }else if !es.haskey(self){
        return 1
    }else if (celt="reset"){ ; Resets the enumeration sequence to the beginning.
        es[self].CurrentElement:=1
    }
    
    if !celt
        celt:=1
    i:=0
    loop % celt ; IEnumString::Next method
    {
        if (es[self].CurrentElement=es[self].List.maxindex()+1)
            break
        string:=es[self].List[es[self].CurrentElement]
        NumPut(p:=DllCall("Ole32\CoTaskMemAlloc","uint",len:=2*(StrPut(string,"utf-16"))),rgelt+(A_Index-1)*A_PtrSize)
        ;DllCall("RtlMoveMemory","ptr",p,"ptr",&string,"uint",len)
        StrPut(string,p,"utf-16")
        NumPut(NumGet(pceltFetched+0,"uint")+1,pceltFetched,0,"uint")
        es[self].CurrentElement:=es[self].CurrentElement+1
        i++
    }
    return (i=celt)?0:1
}

_EnumString_QueryInterface(self,riid,pObj){
    DllCall("Ole32\StringFromCLSID","ptr",riid,"ptr*",sz),string:=StrGet(sz,"utf-16")
    if (string="{00000101-0000-0000-C000-000000000046}"){
        return NumPut(self,pObj+0)*0
    }else return 0x80004002
}

_EnumString_AddRef(self){
    return 1
}

_EnumString_Release(self){
    return 0
}

_EnumString_Skip(self,celt){
    return 0
}

_EnumString_Reset(self){
    AutoComplete(self,"reset",0,0)
    return 0
}

_EnumString_Clone(self,ppenum){
    NumPut(self,ppenum+0)
    return 0
}

Notice also the last post on the original thread
https://autohotkey.com/board/topic/9612 ... -down-list
is also someone asking about crashes... also I've confirmed same effect on Win7, WinXP, ansi, unicode, 32-bit, 64-bit etc...
Attachments
2Find Part Folder... 20160729133736.png
2Find Part Folder... 20160729133736.png (13.87 KiB) Viewed 2237 times
AutoHotkey Unicode 32-bit 20160729131158.png
AutoHotkey Unicode 32-bit 20160729131158.png (11.13 KiB) Viewed 2237 times
AutoHotkey ANSI 32-bit 20160729131641.png
AutoHotkey ANSI 32-bit 20160729131641.png (11.1 KiB) Viewed 2237 times
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
lexikos
Posts: 6212
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

29 Jul 2016, 19:43

Without knowing details of this interface (and lacking the interest to look it up and analyse the DllCalls), the only obvious issue I see is a probable memory leak in _EnumString_QueryInterface:
The caller is responsible for freeing the memory allocated for the string by calling the CoTaskMemFree function.
Source: StringFromCLSID function (COM)
I doubt that would cause crashing, though.
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

29 Jul 2016, 20:17

thanks for looking lexikos

i see... so it should be more like:

Code: Select all

_EnumString_QueryInterface(self,riid,pObj){
    DllCall("Ole32\StringFromCLSID","ptr",riid,"ptr*",sz),string:=StrGet(sz,"utf-16")
    if (string="{00000101-0000-0000-C000-000000000046}"){
        NumPut(self,pObj+0)
        DllCall("ole32\CoTaskMemFree", "Ptr",sz)
        return 0
    }else{
     DllCall("ole32\CoTaskMemFree", "Ptr",sz)
     return 0x80004002
    }
}

the crashing definitely occurs more often when added to my existing script, so i need to finish stripping stuff out to see when the frequency changes
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
lexikos
Posts: 6212
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

30 Jul 2016, 03:10

string contains a copy of the string, so you can replace the two CoTaskMemFree calls with just one before the if statement.
just me
Posts: 5955
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

30 Jul 2016, 03:17

Just a guess:

This

Code: Select all

    }else if (celt="release"){ ; Release autocompletion.
        for k,v in es
        {
            if (v.hwnd=self)
                ObjRelease(v.pac),es.remove(k) ; <<<<< k is an integer key
        }
        return
might cause problems if called while multiple objects are active.

Also, this seems to do it without allocating OLE memory:

Code: Select all

_EnumString_QueryInterface(self,riid,pObj){
   Static IID_IEnumString := 0
        , Init := VarSetCapacity(IID_IEnumString, 16, 0)
                + DllCall("Ole32.dll\IIDFromString", "WStr", "{00000101-0000-0000-C000-000000000046}", "Ptr", &IID_IEnumString)
   If DllCall("Ole32.dll\IsEqualGUID", "Ptr", riid, "Ptr", &IID_IEnumString, "UInt")
      Return NumPut(self,pObj+0)*0
   Else
      Return 0x80004002
}
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

01 Aug 2016, 10:46

thanks for the help guys...
Unfortunately adding "CoTaskMemFree" didn't have any noticeable effect, nor does switching to the "IIDFromString" method from justme for QueryInterface (edit: though both work fine sometimes, and still crash sometimes)

as for the "release" case, i'm not sure what you mean or how to fix it, but I'm not calling the function with "release" so i'm thinking its not causing this problem... i'll try to investigate further
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
just me
Posts: 5955
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

01 Aug 2016, 14:53

Do you call "init" frequently?
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

01 Aug 2016, 15:15

oops, i guess i never posted my example code using the function...

here's a simple example, crashes over 50% of the time i'd say, and "init" is only called once:

Code: Select all

#NoEnv
#SingleInstance, Off
#NoTrayIcon
SetWorkingDir %A_ScriptDir%  


PartNumber := InputBox("Find Part Folder...", "`n   Search for:`n", PartNumber_,0,0,0)

ExitApp
Esc::ExitApp


InputBox(title, text, inputValue = "", owner=0,isPassword=0,buttons=1){
	Global
	GuiID := 8
	If( owner <> 0 ) {
		Gui %owner%:+Disabled
		Gui %GuiID%:+Owner%owner%
	}
   
	Gui, %GuiID%:Color, White
	Gui, %GuiID%:Add, Text, 	x-5  y148 w10 h10  -BackgroundTrans hwndGray -Border
	Gui, %GuiID%:Add, Progress, 	x-5  y147 w10 h3   BackgroundDFDFDF hwndDarkGrayLine

	Gui, %GuiID%:Font, s12 w400, Segoe UI
	Gui, %GuiID%:Add, Text, x12 y10 w320 c002299 BackgroundTrans, %text%

	Gui, %GuiID%:Font, s9 w0, Segoe UI
	Gui, %GuiID%:Add, Edit, % "x12 y+15 w320 h20 -VScroll hwndhedit v_Input_Value" . ((isPassword <> 0) ? " Password":""), %inputValue%

	Gui, %GuiID%:Add, Button, x132 y+25 w100 h45 gInputButton Default, % "OK"
	Gui, %GuiID%:Add, Button, x237 y+-45 w100 h45 gInputButtonCancel, % "Cancel"

	Gui, %GuiID%:-Toolwindow +AlwaysOnTop -MinimizeBox -MaximizeBox -0x80000 +hwndhgui
	Gui, %GuiID%:+LastFound

	Gui %GuiID%:Show,Hide,%title%
 
	WinGetPos, X, Y, Width, Height
	GuiControl, %GuiID%:Move, % DarkGrayLine, % "w" Width+10 " y" Height-87
	GuiControl, %GuiID%:Move, % Gray, % "w" Width+10 " h" Height-133 " y" Height-86
	Control, ExStyle, -0x20000, msctls_progress321

	Gui %GuiID%:Show

;====================================================================
;hedit := DllCall("GetWindow", "Uint", hMyCB, "Uint", 5)
txt = aaa|bbb|ccc|ddd|eee
AutoComplete(hedit,"init",txt,"|")
AutoComplete(hedit,"option","AUTOAPPEND",0)
;====================================================================

	Loop
		If( _Input_Result )
			Break

	If( owner <> 0 )
		Gui %owner%:-Disabled
	Gui, %GuiID%:Submit, Hide

	if (_Input_Result = "OK"){
		Result := _Input_Value
	} else {
		Result := ""
	}
	_Input_Value := ""
	_Input_Result := ""
	Gui %GuiID%:Destroy
  	Return Result

	InputButton:
	  StringReplace _Input_Result, A_GuiControl, &,, All
	Return
	8GuiEscape:
	8GuiClose:
	  _Input_Result := "Close"
	  ExitApp
	Return
	InputButtonCancel:
	  ExitApp
	Return
}
AutoComplete(self, celt, rgelt, pceltFetched)
{
    static es:=[]
    ; =============================================================================================================================================================================
    if (celt = "init")    ; Initializes the autocomplete object.
    {
        sList:=[]
        loop, parse, rgelt, %pceltFetched%, %A_Space%%A_Tab%
            sList[A_Index] := A_LoopField
        obj:=[]
        obj.List := sList
        obj.CurrentElement := 1
        obj.hwnd := self
        obj.SetCapacity("EnumString", A_PtrSize * 8)
        pes := obj.GetAddress("EnumString")
        ,NumPut(pes + A_PtrSize, pes + 0)
        ,NumPut(RegisterCallback("_IUnknown_QueryInterface",  "F"), pes + A_PtrSize * 1)               ; IUnknown::QueryInterface     http://msdn.com/library/ms682521(vs.85,en-us)
        ,NumPut(RegisterCallback("_IUnknown_AddRef",          "F"), pes + A_PtrSize * 2)               ; IUnknown::AddRef             http://msdn.com/library/ms691379(vs.85,en-us)
        ,NumPut(RegisterCallback("_IUnknown_Release",         "F"), pes + A_PtrSize * 3)               ; IUnknown::Release            http://msdn.com/library/ms682317(vs.85,en-us)
        ,NumPut(RegisterCallback(A_ThisFunc,                  "F"), pes + A_PtrSize * 4)               ; IEnumString::Next            http://msdn.com/library/ms693735(vs.85,en-us)
        ,NumPut(RegisterCallback("_IEnumString_Skip",         "F"), pes + A_PtrSize * 5)               ; IEnumString::Skip            http://msdn.com/library/ms688527(vs.85,en-us)
        ,NumPut(RegisterCallback("_IEnumString_Reset",        "F"), pes + A_PtrSize * 6)               ; IEnumString::Reset           http://msdn.com/library/ms686598(vs.85,en-us)
        ,NumPut(RegisterCallback("_IEnumString_Clone",        "F"), pes + A_PtrSize * 7)               ; IEnumString::Clone           http://msdn.com/library/ms693436(vs.85,en-us)
		/*
			ObjIdl.h    #ifdef COBJMACROS
 
			#define IEnumString_QueryInterface(This,riid,ppvObject)	\
				( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) 
 
			#define IEnumString_AddRef(This)	\
				( (This)->lpVtbl -> AddRef(This) ) 
 
			#define IEnumString_Release(This)	\
				( (This)->lpVtbl -> Release(This) ) 
 
 
			#define IEnumString_Next(This,celt,rgelt,pceltFetched)	\
				( (This)->lpVtbl -> Next(This,celt,rgelt,pceltFetched) ) 
 
			#define IEnumString_Skip(This,celt)	\
				( (This)->lpVtbl -> Skip(This,celt) ) 
 
			#define IEnumString_Reset(This)	\
				( (This)->lpVtbl -> Reset(This) ) 
 
			#define IEnumString_Clone(This,ppenum)	\
				( (This)->lpVtbl -> Clone(This,ppenum) ) 
		*/
        pac2 := ComObjCreate("{00BB2763-6A77-11D0-A535-00C04FD7D062}", "{EAC04BC0-3791-11d2-BB95-0060977B464C}")   
        /*
            IAutoComplete2                                                                                                            http://msdn.com/library/bb776288(vs.85,en-us)
 
            CLSID_IAutoComplete = {00BB2763-6A77-11D0-A535-00C04FD7D062}
            IID_IAutoComplete2  = {EAC04BC0-3791-11d2-BB95-0060977B464C}
        */
        obj.pac := pac2
        DllCall(NumGet(NumGet(pac2 + 0) + 3 * A_PtrSize), "Ptr", pac2, "Ptr", self, "Ptr", pes, "Ptr", 0, "Ptr", 0, "Int")
        /*
            IAutoComplete::Init                                                                                                       http://msdn.com/library/bb776293(vs.85,en-us)
 
            HRESULT Init(
                [in]           HWND     hwndEdit,
                [in]           IUnknown *punkACL,
                [in, optional] LPCWSTR  pwszRegKeyPath,
                [in, optional] LPCWSTR  pwszQuickComplete
            );
        */
        es[pes] := obj
        return 0
    }
    ; =============================================================================================================================================================================
    else if (celt = "enable")    ; Enables autocompletion.
    {
        for k, v in es
        {
            if (v.hwnd = self)
                return DllCall(NumGet(NumGet(v.pac + 0) + 4 * A_PtrSize), "Ptr", v.pac, "Int", 1, "Int")
                /*
                    IAutoComplete::Enable                                                                                             http://msdn.com/library/bb776291(vs.85,en-us)
 
                    HRESULT Enable(
                        [in] BOOL fEnable
                    );
                */
        }
        return
    }
    ; =============================================================================================================================================================================
    else if (celt = "disable")    ; Disables autocompletion.
    {
        for k, v in es
        {
            if (v.hwnd = self)
                return DllCall(NumGet(NumGet(v.pac + 0) + 4 * A_PtrSize), "Ptr", v.pac, "Int", 0, "Int")
                /*
                    IAutoComplete::Enable                                                                                             http://msdn.com/library/bb776291(vs.85,en-us)
 
                    HRESULT Enable(
                        [in] BOOL fEnable
                    );
                */
        }
        return
    }
    ; =============================================================================================================================================================================
    else if (celt = "release")    ; Release autocompletion.
    {
        for k, v in es
        {
            if (v.hwnd = self)
                ObjRelease(v.pac), es.remove(k)
        }
        return
    }
    ; =============================================================================================================================================================================
    else if (celt = "option")    ; Sets the current autocomplete options.
    {
        if rgelt is Integer
        {
            if (rgelt < 0x200)
                option := rgelt
            else
                return
        }
        else
        {
            mode := { AUTOSUGGEST:        0x1        ; Enable the autosuggest drop-down list.
                    , AUTOAPPEND:         0x2        ; Enable autoappend.
                    , SEARCH:             0x4        ; Add a search item to the list of completed strings. When the user selects this item, it launches a search engine.
                    , FILTERPREFIXES:     0x8        ; Do not match common prefixes, such as "www." or "http://".
                    , USETAB:             0x10       ; Use the TAB key to select an item from the drop-down list.
                    , UPDOWNKEYDROPSLIST: 0x20       ; Use the UP ARROW and DOWN ARROW keys to display the autosuggest drop-down list.
                    , RTLREADING:         0x40       ; read right-to-left (RTL).
                    , WORD_FILTER:        0x80       ; If set, the autocompleted suggestion is treated as a phrase for search purposes. The suggestion, Microsoft Office, would be treated as "Microsoft Office" (where both Microsoft AND Office must appear in the search results).
                    , NOPREFIXFILTERING:  0x100 }    ; Disable prefix filtering when displaying the autosuggest dropdown. Always display all suggestions.
            option := 0
            loop, parse, rgelt, %A_Space%
                if (mode[A_LoopField])
                    option |= mode[A_LoopField]
        }
        for k, v in es
        {
            if (v.hwnd = self)
                return DllCall(NumGet(NumGet(v.pac + 0) + 5 * A_PtrSize), "Ptr", v.pac, "UInt", option, "Int")
                /*
                    IAutoComplete2::SetOptions                                                                                        http://msdn.com/library/bb776290(vs.85,en-us)
 
                    HRESULT SetOptions(
                        [in] DWORD dwFlag
                    );
                */
        }
        return
    }
    ; =============================================================================================================================================================================
    else if !(es.haskey(self))
    {
        return 1
    }
    ; =============================================================================================================================================================================
    else if (celt = "reset")    ; Resets the enumeration sequence to the beginning.
    {
        es[self].CurrentElement := 1
    }
    ; =============================================================================================================================================================================
    if !(celt)
        celt := 1
    i := 0
    loop % celt                                                                                                ; IEnumString::Next    http://msdn.com/library/ms693735(vs.85,en-us)
    {
        if (es[self].CurrentElement = es[self].List.maxindex() + 1)
            break
        string := es[self].List[es[self].CurrentElement]
        NumPut(p := DllCall("ole32.dll\CoTaskMemAlloc", "UPtr", 2 * (StrPut(string, "UTF-16")), "Ptr"), rgelt + (A_Index - 1) * A_PtrSize) ; http://msdn.com/library/ms692727(vs.85,en-us)
        StrPut(string, p, "UTF-16")
        NumPut(NumGet(pceltFetched + 0, "UInt") + 1, pceltFetched, 0, "UInt")
        es[self].CurrentElement := es[self].CurrentElement + 1
        i++
    }
    return (i = celt) ? 0 : 1
}
 
; =================================================================================================================================================================================
_IUnknown_QueryInterface(self, riid, pObj)                                                              ; IUnknown::QueryInterface    http://msdn.com/library/ms682521(vs.85,en-us)
{
    DllCall("ole32.dll\StringFromCLSID", "Ptr", riid, "Ptr*", sz)                                                                   ; http://msdn.com/library/ms683917(vs.85,en-us)
    string := StrGet(sz, "UTF-16")
    DllCall("ole32\CoTaskMemFree", "Ptr",sz)   ; added per lexikos' recommendation
    if (string = "{00000101-0000-0000-C000-000000000046}")    ; IID_IEnumString = {00000101-0000-0000-C000-000000000046}
        return NumPut(self, pObj + 0) * 0
    else
        return 0x80004002
}
; =================================================================================================================================================================================
_IUnknown_AddRef(self)                                                                                          ; IUnknown::AddRef    http://msdn.com/library/ms691379(vs.85,en-us)
{
    return 1
}
; =================================================================================================================================================================================
_IUnknown_Release(self)                                                                                        ; IUnknown::Release    http://msdn.com/library/ms682317(vs.85,en-us)
{
    return 0
}
; =================================================================================================================================================================================
_IEnumString_Skip(self, celt)                                                                                  ; IEnumString::Skip    http://msdn.com/library/ms688527(vs.85,en-us)
{
    return 0
}
; =================================================================================================================================================================================
_IEnumString_Reset(self)                                                                                      ; IEnumString::Reset    http://msdn.com/library/ms686598(vs.85,en-us)
{
    AutoComplete(self, "reset", 0, 0)
    return 0
}
; =================================================================================================================================================================================
_IEnumString_Clone(self, ppenum)                                                                              ; IEnumString::Clone    http://msdn.com/library/ms693436(vs.85,en-us)
{
    NumPut(self, ppenum + 0)
    return 0
}
; =================================================================================================================================================================================
Last edited by gwarble on 02 Aug 2016, 12:42, edited 2 times in total.
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
Guest

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

01 Aug 2016, 15:25

@gwarble unrelated but someone asked for help with your notify() func, perhaps you could have a look and also repost it on the current script section?
Help request here: https://autohotkey.com/boards/viewtopic.php?f=5&t=19687
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

01 Aug 2016, 16:09

guest: i replied to that thread, thanks

just me, lexikos, and other geniuses: i found a couple versions of this function via old forum thread, german thread, github, etc... some have _IUnknown_QueryInterface() vs others have _EnumString_QueryInterface() but i can't see that making a difference.

some have if (rgelt < 0x200) option := rgelt while some have if (rgelt < 0x200) option := mode which i'm pretty sure the latter is an error

for what is documented as "IEnumString::Next", one has:

Code: Select all

        NumPut(p:=DllCall("Ole32\CoTaskMemAlloc","uint",len:=2*(StrPut(string,"utf-16"))),rgelt+(A_Index-1)*A_PtrSize)
        ;DllCall("RtlMoveMemory","ptr",p,"ptr",&string,"uint",len)
        StrPut(string,p,"utf-16")
while another has

Code: Select all

        NumPut(p := DllCall("ole32.dll\CoTaskMemAlloc", "UPtr", 2 * (StrPut(string, "UTF-16")), "Ptr"), rgelt + (A_Index - 1) * A_PtrSize) ; http://msdn.com/library/ms692727(vs.85,en-us)
        StrPut(string, p, "UTF-16")
and a third version has

Code: Select all

  NumPut(p:=DllCall("Ole32\CoTaskMemAlloc","uint",len:=2*(StrLen(string)+1)),rgelt+(A_Index-1)*A_PtrSize)
  DllCall("RtlMoveMemory","ptr",p,"ptr",&string,"uint",len)
they all seem to act the same but have some considerable differences

thanks again I appreciate the help!
- joel


Edit: this version seems the best in my amateur opinion (and has good comments) but i'm still making sense of the COM stuff (and still having crashes)

Code: Select all

AutoComplete(self, celt, rgelt, pceltFetched)
{
    static es:=[]
    ; =============================================================================================================================================================================
    if (celt = "init")    ; Initializes the autocomplete object.
    {
        sList:=[]
        loop, parse, rgelt, %pceltFetched%, %A_Space%%A_Tab%
            sList[A_Index] := A_LoopField
        obj:=[]
        obj.List := sList
        obj.CurrentElement := 1
        obj.hwnd := self
        obj.SetCapacity("EnumString", A_PtrSize * 8)
        pes := obj.GetAddress("EnumString")
        ,NumPut(pes + A_PtrSize, pes + 0)
        ,NumPut(RegisterCallback("_IUnknown_QueryInterface",  "F"), pes + A_PtrSize * 1)               ; IUnknown::QueryInterface     http://msdn.com/library/ms682521(vs.85,en-us)
        ,NumPut(RegisterCallback("_IUnknown_AddRef",          "F"), pes + A_PtrSize * 2)               ; IUnknown::AddRef             http://msdn.com/library/ms691379(vs.85,en-us)
        ,NumPut(RegisterCallback("_IUnknown_Release",         "F"), pes + A_PtrSize * 3)               ; IUnknown::Release            http://msdn.com/library/ms682317(vs.85,en-us)
        ,NumPut(RegisterCallback(A_ThisFunc,                  "F"), pes + A_PtrSize * 4)               ; IEnumString::Next            http://msdn.com/library/ms693735(vs.85,en-us)
        ,NumPut(RegisterCallback("_IEnumString_Skip",         "F"), pes + A_PtrSize * 5)               ; IEnumString::Skip            http://msdn.com/library/ms688527(vs.85,en-us)
        ,NumPut(RegisterCallback("_IEnumString_Reset",        "F"), pes + A_PtrSize * 6)               ; IEnumString::Reset           http://msdn.com/library/ms686598(vs.85,en-us)
        ,NumPut(RegisterCallback("_IEnumString_Clone",        "F"), pes + A_PtrSize * 7)               ; IEnumString::Clone           http://msdn.com/library/ms693436(vs.85,en-us)
		/*
			ObjIdl.h    #ifdef COBJMACROS

			#define IEnumString_QueryInterface(This,riid,ppvObject)	\
				( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) 

			#define IEnumString_AddRef(This)	\
				( (This)->lpVtbl -> AddRef(This) ) 

			#define IEnumString_Release(This)	\
				( (This)->lpVtbl -> Release(This) ) 


			#define IEnumString_Next(This,celt,rgelt,pceltFetched)	\
				( (This)->lpVtbl -> Next(This,celt,rgelt,pceltFetched) ) 

			#define IEnumString_Skip(This,celt)	\
				( (This)->lpVtbl -> Skip(This,celt) ) 

			#define IEnumString_Reset(This)	\
				( (This)->lpVtbl -> Reset(This) ) 

			#define IEnumString_Clone(This,ppenum)	\
				( (This)->lpVtbl -> Clone(This,ppenum) ) 
		*/
        pac2 := ComObjCreate("{00BB2763-6A77-11D0-A535-00C04FD7D062}", "{EAC04BC0-3791-11d2-BB95-0060977B464C}")   
        /*
            IAutoComplete2                                                                                                            http://msdn.com/library/bb776288(vs.85,en-us)

            CLSID_IAutoComplete = {00BB2763-6A77-11D0-A535-00C04FD7D062}
            IID_IAutoComplete2  = {EAC04BC0-3791-11d2-BB95-0060977B464C}
        */
        obj.pac := pac2
        DllCall(NumGet(NumGet(pac2 + 0) + 3 * A_PtrSize), "Ptr", pac2, "Ptr", self, "Ptr", pes, "Ptr", 0, "Ptr", 0, "Int")
        /*
            IAutoComplete::Init                                                                                                       http://msdn.com/library/bb776293(vs.85,en-us)

            HRESULT Init(
                [in]           HWND     hwndEdit,
                [in]           IUnknown *punkACL,
                [in, optional] LPCWSTR  pwszRegKeyPath,
                [in, optional] LPCWSTR  pwszQuickComplete
            );
        */
        es[pes] := obj
        return 0
    }
    ; =============================================================================================================================================================================
    else if (celt = "enable")    ; Enables autocompletion.
    {
        for k, v in es
        {
            if (v.hwnd = self)
                return DllCall(NumGet(NumGet(v.pac + 0) + 4 * A_PtrSize), "Ptr", v.pac, "Int", 1, "Int")
                /*
                    IAutoComplete::Enable                                                                                             http://msdn.com/library/bb776291(vs.85,en-us)

                    HRESULT Enable(
                        [in] BOOL fEnable
                    );
                */
        }
        return
    }
    ; =============================================================================================================================================================================
    else if (celt = "disable")    ; Disables autocompletion.
    {
        for k, v in es
        {
            if (v.hwnd = self)
                return DllCall(NumGet(NumGet(v.pac + 0) + 4 * A_PtrSize), "Ptr", v.pac, "Int", 0, "Int")
                /*
                    IAutoComplete::Enable                                                                                             http://msdn.com/library/bb776291(vs.85,en-us)

                    HRESULT Enable(
                        [in] BOOL fEnable
                    );
                */
        }
        return
    }
    ; =============================================================================================================================================================================
    else if (celt = "release")    ; Release autocompletion.
    {
        for k, v in es
        {
            if (v.hwnd = self)
                ObjRelease(v.pac), es.remove(k)
        }
        return
    }
    ; =============================================================================================================================================================================
    else if (celt = "option")    ; Sets the current autocomplete options.
    {
        if rgelt is Integer
        {
            if (rgelt < 0x200)
                option := rgelt
            else
                return
        }
        else
        {
            mode := { AUTOSUGGEST:        0x1        ; Enable the autosuggest drop-down list.
                    , AUTOAPPEND:         0x2        ; Enable autoappend.
                    , SEARCH:             0x4        ; Add a search item to the list of completed strings. When the user selects this item, it launches a search engine.
                    , FILTERPREFIXES:     0x8        ; Do not match common prefixes, such as "www." or "http://".
                    , USETAB:             0x10       ; Use the TAB key to select an item from the drop-down list.
                    , UPDOWNKEYDROPSLIST: 0x20       ; Use the UP ARROW and DOWN ARROW keys to display the autosuggest drop-down list.
                    , RTLREADING:         0x40       ; read right-to-left (RTL).
                    , WORD_FILTER:        0x80       ; If set, the autocompleted suggestion is treated as a phrase for search purposes. The suggestion, Microsoft Office, would be treated as "Microsoft Office" (where both Microsoft AND Office must appear in the search results).
                    , NOPREFIXFILTERING:  0x100 }    ; Disable prefix filtering when displaying the autosuggest dropdown. Always display all suggestions.
            option := 0
            loop, parse, rgelt, %A_Space%
                if (mode[A_LoopField])
                    option |= mode[A_LoopField]
        }
        for k, v in es
        {
            if (v.hwnd = self)
                return DllCall(NumGet(NumGet(v.pac + 0) + 5 * A_PtrSize), "Ptr", v.pac, "UInt", option, "Int")
                /*
                    IAutoComplete2::SetOptions                                                                                        http://msdn.com/library/bb776290(vs.85,en-us)

                    HRESULT SetOptions(
                        [in] DWORD dwFlag
                    );
                */
        }
        return
    }
    ; =============================================================================================================================================================================
    else if !(es.haskey(self))
    {
        return 1
    }
    ; =============================================================================================================================================================================
    else if (celt = "reset")    ; Resets the enumeration sequence to the beginning.
    {
        es[self].CurrentElement := 1
    }
    ; =============================================================================================================================================================================
    if !(celt)
        celt := 1
    i := 0
    loop % celt                                                                                                ; IEnumString::Next    http://msdn.com/library/ms693735(vs.85,en-us)
    {
        if (es[self].CurrentElement = es[self].List.maxindex() + 1)
            break
        string := es[self].List[es[self].CurrentElement]
        NumPut(p := DllCall("ole32.dll\CoTaskMemAlloc", "UPtr", 2 * (StrPut(string, "UTF-16")), "Ptr"), rgelt + (A_Index - 1) * A_PtrSize) ; http://msdn.com/library/ms692727(vs.85,en-us)
        StrPut(string, p, "UTF-16")
        NumPut(NumGet(pceltFetched + 0, "UInt") + 1, pceltFetched, 0, "UInt")
        es[self].CurrentElement := es[self].CurrentElement + 1
        i++
    }
    return (i = celt) ? 0 : 1
}

; =================================================================================================================================================================================
_IUnknown_QueryInterface(self, riid, pObj)                                                              ; IUnknown::QueryInterface    http://msdn.com/library/ms682521(vs.85,en-us)
{
    DllCall("ole32.dll\StringFromCLSID", "Ptr", riid, "Ptr*", sz)                                                                   ; http://msdn.com/library/ms683917(vs.85,en-us)
    string := StrGet(sz, "UTF-16")
    DllCall("ole32\CoTaskMemFree", "Ptr",sz)   ; added per lexikos' recommendation
    if (string = "{00000101-0000-0000-C000-000000000046}")    ; IID_IEnumString = {00000101-0000-0000-C000-000000000046}
        return NumPut(self, pObj + 0) * 0
    else
        return 0x80004002
}
; =================================================================================================================================================================================
_IUnknown_AddRef(self)                                                                                          ; IUnknown::AddRef    http://msdn.com/library/ms691379(vs.85,en-us)
{
    return 1
}
; =================================================================================================================================================================================
_IUnknown_Release(self)                                                                                        ; IUnknown::Release    http://msdn.com/library/ms682317(vs.85,en-us)
{
    return 0
}
; =================================================================================================================================================================================
_IEnumString_Skip(self, celt)                                                                                  ; IEnumString::Skip    http://msdn.com/library/ms688527(vs.85,en-us)
{
    return 0
}
; =================================================================================================================================================================================
_IEnumString_Reset(self)                                                                                      ; IEnumString::Reset    http://msdn.com/library/ms686598(vs.85,en-us)
{
    AutoComplete(self, "reset", 0, 0)
    return 0
}
; =================================================================================================================================================================================
_IEnumString_Clone(self, ppenum)                                                                              ; IEnumString::Clone    http://msdn.com/library/ms693436(vs.85,en-us)
{
    NumPut(self, ppenum + 0)
    return 0
}
; =================================================================================================================================================================================
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
just me
Posts: 5955
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

02 Aug 2016, 01:52

gwarble wrote:oops, i guess i never posted my example code using the function...

here's a simple example, crashes over 50% of the time i'd say, and "init" is only called once:

...
"init" is called once per call of InputBox(). On every call new IAutoComplete2 and also IEnumString interfaces are created. But due to the lack of a "release" call they are never freed. Also, the callback functions are newly registered on every call. I suppose this might cause problems after some amount of calls.

You might try to register the callback functions only once and to change "release" to

Code: Select all

    ; =============================================================================================================================================================================
    else if (celt = "release")    ; Release autocompletion.
    {
        for k, v in es
        {
            if (v.hwnd = self)
                ObjRelease(v.pac), es.delete(k) ; <<<<< replaced remove with delete though it doesn't matter in your case
        }
        return
    }
and to call it before the function InputBox() returns.

After looking at your question and the AutoComplete() code I tried to create an own class-based version. While testing I had to fight with two bugs:
  1. Autocompletion didn't work without errors.
    In this case the IEnumString interface did not pass any strings to IAutoComplete2.
  2. The script crashed with AutoHotkey ... has stopped working
    In this case corrupt pointers where passed to IAutoComplete2.
Would you like to test my version?
just me
Posts: 5955
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

02 Aug 2016, 06:39

I changed my test script to work like yours. And I ran into similar problems. After the Gui and the IAutoComplete2 interface have been created and destroyed/released several (different) times, even without typing in the edit, my script using my own class-based version silently dies. I don't know the reason as yet.
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

02 Aug 2016, 10:57

sure i'd love to try your class version...

i have to admit i'm glad to hear the problem repeats for you and it wasn't a simple oversight on my end...

true "init" is called once per function call, but the function is only called once and the problem is apparent... i figured there would be some cleanup required for using it multiple times that i would work out in the future, but for now i'm just trying to use it accurately and repeatedly in a simple case before i get to that point

hopefully we can figure out the reason for the silent death and/or "stopped working", thanks, i appreciate the help
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
just me
Posts: 5955
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

02 Aug 2016, 11:16

Well, here's my current attempt which seems to be a bit more stable:

Code: Select all

#NoEnv
SetBatchLines, -1
Gui, New, hwndHGUI
Gui, Add, Edit, w300 hwndHEDIT
Gui, Add, Button, wp gUpdStrings, Update Strings
Gui, Add, Button, wp gToggleAC, Toggle Autocomplete
Gui, Add, StatusBar, , Active!
Gui, Show, , Test
ACActive := True
Strings := "I'm fine`nThat's ok`nThank you`nOh my god`nEinstein`nEireen"
AC1 := New AutoComplete(HEDIT, StrSplit(Strings, "`n", "`r`t "), , True)
Return

ToggleAC:
ACActive := !ACActive
AC1 := ACActive ? New AutoComplete(HEDIT, StrSplit(Strings, "`n", "`r`t "), , True) : ""
SB_SetText((ACActive ? "Active!" : "Not active!") . " - " . IsObject(AC1))
Return

GuiClose:
If (ACActive)
   AC1 := ""
ExitApp
Gui, Destroy
Return

UpdStrings:
Strings2 := "I'm hungry`nThat's not ok`nThank you very much`nOh my god`nEinstein was here`nEireen went away"
AC1.UpdStrings(StrSplit(Strings2, "`n", "`r`t "))
GuiControl, Focus, %HEDIT%
Return

Esc::
ExitApp

; ==================================================================================================================================
; Class-based version of a script by nepter
; -> autohotkey.com/board/topic/96129-ahk-l-custom-autocompletion-for-edit-control-with-drop-down-list/
; MSDN:
;     IAutoComplete        -> msdn.microsoft.com/en-us/library/bb776292(v=vs.85).aspx
;     IAutoComplete2       -> msdn.microsoft.com/en-us/library/bb776288(v=vs.85).aspx
;     AUTOCOMPLETEOPTIONS  -> msdn.microsoft.com/en-us/library/bb762479(v=vs.85).aspx
;     IEnumString          -> msdn.microsoft.com/en-us/library/ms687257(v=vs.85).aspx
; ==================================================================================================================================
; Creates a new autocompletion object (lib compatible).
; Parameters:
;     HEDT        -  Handle to an edit control.
;     Strings     -  Simple array of autocompletion strings.
;     Options     -  Simple array of autocomplete options (see SetOptions() method).
;                    Default: "" -> AUTOSUGGEST
;     WantReturn  -  Set to True to pass the Return key to single-line edit controls to close the autosuggest drop-down list.
;                    Note: The edit control will be subclassed in this case.
;                    Default: False
;     Enable      -  By default, autocompletion will be enabled after creation. Pass False to disable it.
; Return values:
;     On success, the function returns a new instance of the AutoComplete class; otherwise an empty string.
; ==================================================================================================================================
AutoComplete_Create(HEDT, Strings, Options := "", WantReturn := False, Enable := True) {
   Return New AutoComplete(HEDT, Strings, Options, WantReturn, Enable)
}
; ==================================================================================================================================
; Used internally to pass the return key to single-line edit controls.
; ==================================================================================================================================
AutoComplete_SubclassProc(HWND, Msg, wParam, lParam, ID, Data) {
   If (Msg = 0x0087) && (wParam = 13) ; WM_GETDLGCODE, VK_RETURN
      Return 0x0004 ; DLGC_WANTALLKEYS
   Return DllCall("Comctl32.dll\DefSubclassProc", "Ptr", HWND, "UInt", Msg, "Ptr", wParam, "Ptr", lParam)
}
; ==================================================================================================================================
Class AutoComplete {
   Static Attached := []
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Constructor - see AutoComplete_Create()
   ; -------------------------------------------------------------------------------------------------------------------------------
   __New(HEDT, Strings, Options := "", WantReturn := False, Enable := True) {
      Static IAC2_Init := A_PtrSize * 3
      Static IES_QueryInterface := RegisterCallback("IEnumString_QueryInterface")
           , IES_AddRef := RegisterCallback("IEnumString_AddRef")
           , IES_Release := RegisterCallback("IEnumString_Release")
           , IES_Next := RegisterCallback("IEnumString_Next")
           , IES_Skip := RegisterCallback("IEnumString_Skip")
           , IES_Reset := RegisterCallback("IEnumString_Reset")
           , IES_Clone := RegisterCallback("IEnumString_Clone")
      If AutoComplete.Attached[HEDT]
         Return False
      AutoComplete.Attached[HEDT] := True
      This.HWND := HEDT
      This.SetCapacity("IES", A_PtrSize * 10)
      This.PIES := Addr := This.GetAddress("IES")
      Addr := NumPut(Addr + A_PtrSize, Addr + 0, "Uptr")
      Addr := NumPut(IES_QueryInterface , Addr + 0, "Uptr")
      Addr := NumPut(IES_AddRef, Addr + 0, "Uptr")
      Addr := NumPut(IES_Release, Addr + 0, "Uptr")
      Addr := NumPut(IES_Next, Addr + 0, "Uptr")
      Addr := NumPut(IES_Skip, Addr + 0, "Uptr")
      Addr := NumPut(IES_Reset, Addr + 0, "Uptr")
      Addr := NumPut(IES_Clone,Addr + 0, "Uptr")
      Addr := NumPut(0, Addr + 0, "Uptr")
      StrArr := {Current: 0}
      If IsObject(Strings)
         Loop, % Strings.Length()
            If (Str := Strings[A_Index])
               StrArr.Push({S: Str, L: StrPut(Str, "UTF-16") * 2})
            Else
               Break
      NumPut(Object(StrArr), This.PIES + (A_PtrSize * 9), "UPtr")
      If !(This.IAC2 := ComObjCreate("{00BB2763-6A77-11D0-A535-00C04FD7D062}", "{EAC04BC0-3791-11d2-BB95-0060977B464C}"))
         Return ""
      This.VTBL := NumGet(This.IAC2 + 0, "UPtr")
      If DllCall(NumGet(This.VTBL + IAC2_Init, "UPtr"), "Ptr", This.IAC2, "Ptr", HEDT, "Ptr", This.PIES, "Ptr", 0, "Ptr", 0, "UInt")
         Return ""
      This.SetOptions(Options = "" ? ["AUTOSUGGEST"] : Options)
      If !(Enable)
         This.Disable()
      If (WantReturn) {
         ControlGet, Styles, Style, , , ahk_id %HEDT%
         If !(Styles & 0x0004) && (CB := RegisterCallback("AutoComplete_SubclassProc")) { ; !ES_MULTILINE
            If DllCall("SetWindowSubclass", "Ptr", HEDT, "Ptr", CB, "Ptr", &This, "Ptr", 0, "UInt")
               This.SubclassProc := CB
            Else
               DllCall("GlobalFree", "Ptr", CB, "Ptr")
         }
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Destructor
   ; -------------------------------------------------------------------------------------------------------------------------------
   __Delete() {
      AutoComplete.Attached.Delete(This.HWND)
      If (This.IAC2) {
         This.Disable()
         ObjRelease(This.IAC2)
      }
      If (This.PIES)
         Try ObjRelease(NumGet(This.PIES + (A_PtrSize * 9), "UPtr"))
      If (This.SubclassProc) {
         DllCall("RemoveWindowSubclass", "Ptr", This.HWND, "Ptr", This.SubclassProc, "Ptr", &This)
         DllCall("GlobalFree", "Ptr", This.SubclassProc, "Ptr")
      }
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Enables / disables autocompletion.
   ;     Enable   -  True or False
   ; -------------------------------------------------------------------------------------------------------------------------------
   Enable(Enable := True) {
      Static IAC2_Enable := A_PtrSize * 4
      If !(This.VTBL)
         Return False
      This.Enabled := !!Enable
      Return !DllCall(NumGet(This.VTBL + IAC2_Enable, "UPtr"), "Ptr", This.IAC2, "Int", !!Enable, "UInt")
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Disables autocompletion.
   ; -------------------------------------------------------------------------------------------------------------------------------
   Disable() {
      Return This.Enable(False)
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ;  Sets the autocompletion options.
   ;     Options  -  simple array of option strings corresponding to the keys defined in ACO
   ; -------------------------------------------------------------------------------------------------------------------------------
   SetOptions(Options) {
      Static IAC2_SetOptions := A_PtrSize * 5
      ; SEARCH (4) is not supported!
      Static ACO := {NONE: 0, AUTOSUGGEST: 1, AUTOAPPEND: 2, FILTERPREFIXES: 8, USETAB: 16
                   , UPDOWNKEYDROPSLIST: 32, RTLREADING: 64, WORD_FILTER: 128, NOPREFIXFILTERING: 256}
      If !(This.VTBL)
         Return False
      Opts := 0
      For Each, Opt In Options
         Opts |= (Opt := ACO[Opt]) <> "" ? Opt : 0
      Return !DllCall(NumGet(This.VTBL + IAC2_SetOptions, "UPtr"), "Ptr", This.IAC2, "UInt", Opts, "UInt")
   }
   ; -------------------------------------------------------------------------------------------------------------------------------
   ; Updates the autocompletion strings.
   ;     Strings  -  simple array of strings
   ; Note: The method clears the edit control.
   ; -------------------------------------------------------------------------------------------------------------------------------
   UpdStrings(Strings, Clear := True) {
      If !(This.PIES)
         Return False
      Try ObjRelease(NumGet(This.PIES + (A_PtrSize * 9), "UPtr"))
      ControlSetText, , , % "ahk_id " . This.HWND
      StrArr := {Current: 0}
      If IsObject(Strings)
         Loop, % Strings.Length()
            If (Str := Strings[A_Index])
               StrArr.Push({S: Str, L: StrPut(Str, "UTF-16") * 2})
            Else
               Break
      NumPut(Object(StrArr), This.PIES + (A_PtrSize * 9), "UPtr")
      Return True
   }
}
; ==================================================================================================================================
IEnumString_QueryInterface(IES, RIID, ObjPtr) {
   Static IID := "{00000101-0000-0000-C000-000000000046}", IID_IEnumString := 0
        , Init := VarSetCapacity(IID_IEnumString, 16, 0) + DllCall("Ole32.dll\IIDFromString", "WStr", IID, "Ptr", &IID_IEnumString)
   If DllCall("Ole32.dll\IsEqualGUID", "Ptr", RIID, "Ptr", &IID_IEnumString, "UInt")
      Return !(NumPut(IES, ObjPtr + 0, "UPtr"))
   Else
      Return 0x80004002
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_AddRef(IES) {
   Return 1
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Release(IES) {
   Return 0
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Next(IES, Fetch, Strings, Fetched) {
   StrTbl := Object(NumGet(IES + (A_PtrSize * 9), "UPtr")), I := 0
   While (StrTbl.Current < StrTbl.MaxIndex()) && (I < Fetch)
      StrTbl.Current++
      , Str := StrTbl[StrTbl.Current]
      , Ptr := DllCall("Ole32.dll\CoTaskMemAlloc", "Ptr", Str.L, "UPtr")
      , StrPut(Str.S, Ptr, "UTF-16")
      , NumPut(Ptr, Strings + (I * A_PtrSize), "Ptr")
      , NumPut(++I, Fetched + 0, "UInt")
   Return (I = Fetch) ? 0 : 1
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Skip(IES, Count) {
   StrTbl := Object(NumGet(NumGet(IES + 0, "UPtr") + (A_PtrSize * 8), "UPtr"))
   If ((StrTbl.Current + Count) <= StrTbl.MaxIndex())
      Return !(StrTbl.Current += Count)
   Return 1
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Reset(IES) {
   StrTbl := Object(NumGet(IES + (A_PtrSize * 9), "UPtr"))
   StrTbl.Current := 0
   Return 0
}
; ----------------------------------------------------------------------------------------------------------------------------------
IEnumString_Clone(IES, ObjPtr) { ; I don't think this will work properly!
   Return !(NumPut(IES, ObjPtr + 0, "UPtr"))
}
; ==================================================================================================================================
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

02 Aug 2016, 13:21

Wow, nice rewrite (and fast)

Still have some crashing going on unfortunately, especially when put into my "inputbox" example

And finally, if you're planning to polish off the class, i'd think a WantEsc option (like WantReturn but not appending the autocomplete) would be useful (and better mimic windows explorer's usage)

I will try to test/understand further this evening, thanks


Edit: i wonder if this is a bigger issue:
http://www.yqcomputer.com/428_2255_1.htm
http://www.purebasic.fr/english/viewtop ... 1&start=45
http://www.archivum.info/microsoft.publ ... times.html
http://stackoverflow.com/questions/1878 ... sh-dataset
http://www.archivum.info/microsoft.publ ... ition.html
https://www.autoitscript.com/forum/topi ... ocomplete/

http://stackoverflow.com/questions/3018 ... ted-memory
http://www.forums.purebasic.com/english ... b&start=15

Edit: interesting information to note: crashes seem to only happen on first character entered... once the first character takes without crashing, the AutoComplete seems to work as expected... but then selecting all and typing a new first character will often crash it again

Edit: testing on an XP machine, noticed the same code seems to crash less often, but still maybe 25% of the time... noticed harder crashes though that would freeze up task manager and give error on forcing the process closed... saw that drwatson was running, this is from the log:

Code: Select all

Application exception occurred:
        App: C:\Programs\AutoHotkey\AutoHotkey.exe (pid=2516)
        When: 8/2/2016 @ 20:28:55.852
        Exception number: c0000005 (access violation)

*----> System Information <----*
        Computer Name: ...
        User Name: ...
        Terminal Session Id: 0
        Number of Processors: 2
        Processor Type: x86 Family 15 Model 67 Stepping 3
        Windows Version: 5.1
        Current Build: 2600
        Service Pack: 3
        Current Type: Multiprocessor Free
        Registered Organization: ...
        Registered Owner: ...

*----> Task List <----*
   0 System Process
   4 System
 624 smss.exe
 672 csrss.exe
 696 winlogon.exe
 740 services.exe
 752 lsass.exe
 912 svchost.exe
 948 svchost.exe
1068 svchost.exe
1160 svchost.exe
1296 spoolsv.exe
1384 httpd.exe
1408 FileZilla Server.exe
1484 WinVNC4.exe
1656 httpd.exe
1248 Explorer.EXE
1564 ctfmon.exe
1580 EitherMouse.exe
1736 TrayDate.exe
 176 svchost.exe
3092 firefox.exe
 412 plugin-container.exe
3132 notepad.exe
2516 AutoHotkey.exe
1204 drwtsn32.exe

*----> Module List <----*
(0000000000400000 - 00000000004e9000: C:\Programs\AutoHotkey\AutoHotkey.exe
(000000005ad70000 - 000000005ada8000: C:\WINDOWS\system32\uxtheme.dll
(00000000605d0000 - 00000000605d9000: C:\WINDOWS\system32\mslbui.dll
(0000000071aa0000 - 0000000071aa8000: C:\WINDOWS\system32\WS2HELP.dll
(0000000071ab0000 - 0000000071ac7000: C:\WINDOWS\system32\WS2_32.dll
(0000000071ad0000 - 0000000071ad9000: C:\WINDOWS\system32\WSOCK32.dll
(0000000074720000 - 000000007476c000: C:\WINDOWS\system32\MSCTF.dll
(00000000755c0000 - 00000000755ee000: C:\WINDOWS\system32\msctfime.ime
(0000000075f80000 - 000000007607d000: C:\WINDOWS\system32\browseui.dll
(0000000076390000 - 00000000763ad000: C:\WINDOWS\system32\IMM32.DLL
(00000000763b0000 - 00000000763f9000: C:\WINDOWS\system32\COMDLG32.dll
(0000000076b40000 - 0000000076b6d000: C:\WINDOWS\system32\WINMM.dll
(0000000076bf0000 - 0000000076bfb000: C:\WINDOWS\system32\PSAPI.DLL
(0000000076fd0000 - 000000007704f000: C:\WINDOWS\system32\CLBCATQ.DLL
(0000000077050000 - 0000000077115000: C:\WINDOWS\system32\COMRes.dll
(0000000077120000 - 00000000771ab000: C:\WINDOWS\system32\OLEAUT32.dll
(00000000773d0000 - 00000000774d3000: C:\WINDOWS\WinSxS\x86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.2600.6028_x-ww_61e65202\COMCTL32.dll
(00000000774e0000 - 000000007761e000: C:\WINDOWS\system32\ole32.dll
(0000000077b40000 - 0000000077b62000: C:\WINDOWS\system32\Apphelp.dll
(0000000077c00000 - 0000000077c08000: C:\WINDOWS\system32\VERSION.dll
(0000000077c10000 - 0000000077c68000: C:\WINDOWS\system32\msvcrt.dll
(0000000077dd0000 - 0000000077e6b000: C:\WINDOWS\system32\ADVAPI32.dll
(0000000077e70000 - 0000000077f03000: C:\WINDOWS\system32\RPCRT4.dll
(0000000077f10000 - 0000000077f59000: C:\WINDOWS\system32\GDI32.dll
(0000000077f60000 - 0000000077fd6000: C:\WINDOWS\system32\SHLWAPI.dll
(0000000077fe0000 - 0000000077ff1000: C:\WINDOWS\system32\Secur32.dll
(000000007c800000 - 000000007c8f6000: C:\WINDOWS\system32\kernel32.dll
(000000007c900000 - 000000007c9b2000: C:\WINDOWS\system32\ntdll.dll
(000000007c9c0000 - 000000007d1d8000: C:\WINDOWS\system32\SHELL32.dll
(000000007e410000 - 000000007e4a1000: C:\WINDOWS\system32\USER32.dll

*----> State Dump for Thread Id 0xf48 <----*

eax=00000000 ebx=00220266 ecx=00000000 edx=ec00f140 esi=0006db80 edi=00000000
eip=00460078 esp=008e9f40 ebp=008e9f50 iopl=0         ov up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000a82

*** WARNING: Unable to verify checksum for C:\Programs\AutoHotkey\AutoHotkey.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Programs\AutoHotkey\AutoHotkey.exe
function: AutoHotkey
        00460058 080f             or      [edi],cl
        0046005a 95               xchg    eax,ebp
        0046005b c1897dd0c745c0   ror     dword ptr [ecx+0x45c7d07d],0xc0
        00460062 681f4b0089       push    0x89004b1f
        00460067 7de8             jge     AutoHotkey+0x60051 (00460051)
        00460069 897d90           mov     [ebp-0x70],edi
        0046006c 897da8           mov     [ebp-0x58],edi
        0046006f 49               dec     ecx
        00460070 230dd0954d00     and     ecx,[AutoHotkey+0xd95d0 (004d95d0)]
        00460076 03d2             add     edx,edx
FAULT ->00460078 8b74d00c         mov   esi,[eax+edx*8+0xc] ds:0023:60078a0c=????????
        0046007c 8d44d00c         lea     eax,[eax+edx*8+0xc]
        00460080 894d8c           mov     [ebp-0x74],ecx
        00460083 837e0841         cmp     dword ptr [esi+0x8],0x41
        00460087 8975e0           mov     [ebp-0x20],esi
        0046008a 0f84201c0000     je      AutoHotkey+0x61cb0 (00461cb0)
        00460090 b810000000       mov     eax,0x10
        00460095 e836a10400       call    AutoHotkey+0xaa1d0 (004aa1d0)
        0046009a 8b0e             mov     ecx,[esi]
        0046009c 8bdc             mov     ebx,esp
        0046009e 890b             mov     [ebx],ecx

*----> Stack Back Trace <----*
Last edited by gwarble on 04 Aug 2016, 00:28, edited 2 times in total.
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
just me
Posts: 5955
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

03 Aug 2016, 04:32

Hi gwarble,
crashes seem to only happen on first character entered...
That's exactly the moment when IAutoComplete calls IEnumString to get and filter the list of suggestions. It's done only once for the first character in the edit.

When testing your InputBox() sample, the script crashes much less often here, if you add a short sleep to this loop:

Code: Select all

	Loop {
		If( _Input_Result )
			Break
		Sleep, 10 ; added
   }
Edit:
And finally, if you're planning to polish off the class, i'd think a WantEsc option (like WantReturn but not appending the autocomplete) would be useful (and better mimic windows explorer's usage)
Return key handling is "built-in". You only need to pass the key to the edit. But IAutoComplete doesn't seem to handle the Esc key internally.
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

03 Aug 2016, 11:45

hey just me,
Thanks, you're right that sleep makes a considerable difference in the crash frequency... seems somethings keeping the callbacks from getting called properly?

I noticed that (tried adding a check for 27 (vk_esc) like your check for 13 (vk_return) but it never triggered... i guess that will have to be worked around with a hotkey
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
lexikos
Posts: 6212
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

03 Aug 2016, 16:40

The callback is being called on the wrong thread. Even if you do everything else correctly, this will cause instability.

I suppose there are at least two solutions:
  • Write the core functionality in another language and compile as a DLL.
  • Write a simple native code callback that uses window messages or similar to synchronise with AutoHotkey's main thread, and compile it to machine code (keyword "MCode"). (The script would have to fill in the address of SendMessage within the compiled code.)
gwarble
Posts: 323
Joined: 30 Sep 2013, 15:01

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

03 Aug 2016, 20:57

hmm... well thats a bummer. so the iAutoComplete2 interface (or autocomplete object i guess) creates a second thread, in which the callback needs to be registered but we don't have access to it to do that?

that might be reason enough to create a mimic and code it all... but i like how its identical to what people are used to in explorer edit/combo boxes

i like your solution ideas, thanks... unfortunately both are probably out of my league (but i'll look into it)
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
lexikos
Posts: 6212
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: AutoComplete() (using iAutoComplete2) crashes script/ahk sometimes

03 Aug 2016, 22:24

No, the callback does not need to be "registered". It needs to be called from the correct thread. AutoHotkey's script engine is not thread-safe.

Return to “Ask For Help”

Who is online

Users browsing this forum: electrone77, Google [Bot], MannyKSoSo, scriptor2016, sinkfaze, SOTE and 169 guests