Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Scripting.Dictionary Object as Associative Array


  • Please log in to reply
62 replies to this topic
Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
I think the Scripting.Dictionary COM object is already an associative array, so, may be used as the replacement until AHK has a built-in support of it.

DOWNLOAD Dictionary.ahk and CoHelper.ahk.

olfen
  • Members
  • 115 posts
  • Last active: Dec 25 2012 09:48 AM
  • Joined: 04 Jun 2005
Very nice. Thank you!

David Andersen
  • Members
  • 140 posts
  • Last active: Jun 28 2011 04:54 PM
  • Joined: 15 Jul 2005
Thanks a lot Sean. Do you have any idea what the performance would be using thousands of words? Does it depend on any libraries?

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

Thanks a lot Sean. Do you have any idea what the performance would be using thousands of words? Does it depend on any libraries?

Thanks. I think it uses a hash table, so basically the look-up time should be independent on the number of items in the table. In reality, however, there could be collisions among them, depending on the implementations, so it could grow linearly with the number of items in the worst case, but I don't think it would ever happen.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
It works perfectly! Thanks for sharing this excellent list of functions. I have a few questions, though.

- What are those magic numbers in the Dictionary function, like {EE09B103-97E0-11CF-978F-00A02463E06F}? (Where can we read about them?)
- Are they the same for every Windows version?
- What are computed by the functions Items() and Keys()?
- If you call "SafeArrayDestroy" at exit, will it invalidate their result?

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006

- What are those magic numbers in the Dictionary function, like {EE09B103-97E0-11CF-978F-00A02463E06F}? (Where can we read about them?)

ITs called GUID, its just a unique number COM uses for id purposes. The human friendly name is called ProgId.


- Are they the same for every Windows version?

ofc
Posted Image

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

It works perfectly! Thanks for sharing this excellent list of functions. I have a few questions, though.

Thanks.

- What are those magic numbers in the Dictionary function, like {EE09B103-97E0-11CF-978F-00A02463E06F}? (Where can we read about them?)
- Are they the same for every Windows version?

They are Class Identifier and Interface Identifier in GUID form.
The CLSID should be registered in the registry to be used, however, not neccessarily for IID but mostly registered too.
If the COM interface is of IDispatch type, then you may prefer to get them through the TypeLib using oleview.exe or tlb.exe mentioned here:
<!-- m -->http://www.autohotke... ... c&start=15<!-- m -->

And, yes, you may take it granted that they are version independent.

- What are computed by the functions Items() and Keys()?
- If you call "SafeArrayDestroy" at exit, will it invalidate their result?

They return all defined keys/values in VB's Type-Safe arrays.
<!-- m -->http://msdn2.microso...y/ms221482.aspx<!-- m -->

As the SafeArray is not that intuitive to use comparing with the array in C/C++ (:MS itself urges to use dedicated functions to manipulate them), I don't recommend using them unless absolutely neccessary. I recommend using Enumerate() instead.
BTW, I don't think destroying the SafeArrays will affect the Dictionary itself.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Thanks for the quick reply.

I don't think destroying the SafeArrays will affect the Dictionary itself.

I did not mean that the dictionary was affected, but a SafeArray was returned (a pointer to a structure?), which just had been destroyed. Is the pointer after returning from the function is still pointing to allocated memory?

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

I did not mean that the dictionary was affected, but a SafeArray was returned (a pointer to a structure?), which just had been destroyed. Is the pointer after returning from the function is still pointing to allocated memory?

In my tests, the memory pointed, which is the descriptor of the SafeArray, could still be accessed until the object itself was released. However, the releavant data contained in it were cleared out.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Thanks for these great functions. I've linked to this topic from the bottom of the Arrays page. Does anyone know whether Windows 9x/NT supports Scripting.Dictionary?

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
2Chris
Why do you still support Win9x when even MS droped support for it long time ago ?
Posted Image

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
I edited a little Sean's great script: changed some function names, added more test cases, replaced the enumerate function with NextKey (which is of lower level, providing more flexibility) removed the safe array functions (which don't work for me) and copied over the used COM helper functions, so there will be no dependency from another file. (If your project uses other COM functions, you may prefer the original #include.) This script is still Sean's, only written in another style:
DllCall("ole32\CoInitialize", UInt,0)
pdic := CreateDictionary()                 ; param = 1: Case of Key is ignored. 0 (Default): case-sensitive

Add(pdic,"Test1", "This is a Test1")
Add(pdic,"Test2", "This is a Test2")
Add(pdic,"Test1", "This is a Test3")       ; no effect, already exists
Add(pdic,"Test3", "This is a Test9")
Add(pdic,"Test0", "")

Set(pdic,"Result1", "This is a Result1")   ; create and set
Set(pdic,"Result2", "This is a Result2")
Set(pdic,"Result1", "This is a Result3")   ; overwrite

Rename(pdic, "Result1", "Result3")
Remove(pdic, "Test3")

MsgBox, % "Hash of 'Test9': " . HashVal(pdic,"Test9")
MsgBox, % "Count: " . Count(pdic)

MsgBox % "Test1:"  . Get(pdic,"Test1")
MsgBox % "test2:"  . Get(pdic,"test2")     ; empty

Loop {
  k := NextKey(penum,pdic)
  IfEqual penum,, Break                    ; k (Itm) can be empty
  MsgBox % "[1/"  . A_Index . "]" . k . ":" . Get(pdic,k)
}
Loop % Count(pdic) {
  k := NextKey(penum, pdic)
  MsgBox % "[2a/" . A_Index . "]" . k . ":" . Get(pdic,k)
  k := NextKey(penu1,pdic)
  MsgBox % "[2b/" . A_Index . "]" . k . ":" . Get(pdic,k)
}

DestroyDictionary(pdic)
DllCall("ole32\CoUninitialize")
Return

CreateDictionary(nCompMode = 0) { ; Compare mode = 0: Binary, 1: Text, 2: Database, n: LCID
   CLSID_Dictionary := "{EE09B103-97E0-11CF-978F-00A02463E06F}"
   IID_IDictionary  := "{42C642C1-97E1-11CF-978F-00A02463E06F}"
   pdic := CreateObject(CLSID_Dictionary, IID_IDictionary)
   DllCall(VTable(pdic,18), UInt,pdic, Int,nCompMode) ; Set compare mode
   Return pdic
}

DestroyDictionary(pdic) {
   DllCall([email protected]([email protected](pdic)+8), UInt,pdic)
}

Add(pdic, sKey, sItm) {  ; If key exists: no effect (<--> Set)
   AllocBString(pKey, var1, sKey)
   AllocBString(pItm, var2, sItm)
   DllCall(VTable(pdic,10), UInt,pdic, UInt,&var1, UInt,&var2)
   DllCall("oleaut32\SysFreeString", UInt,pKey)
   DllCall("oleaut32\SysFreeString", UInt,pItm)
}

Set(pdic, sKey, sItm) {  ; If key exists, update the item, Else create a new entry
   AllocBString(pKey, var1, sKey)
   AllocBString(pItm, var2, sItm)
   DllCall(VTable(pdic,8), UInt,pdic, UInt,&var1, UInt,&var2)  ; 8 (Set0 -> 7)
   DllCall("oleaut32\SysFreeString", UInt,pKey)
   DllCall("oleaut32\SysFreeString", UInt,pItm)
}

Get(pdic, sKey) {  ; empty if not exists
   AllocBString(pKey, var1, sKey)
   DllCall(VTable(pdic,12), UInt,pdic, UInt,&var1, IntP,bExist)
   If bExist {     ; to avoid creating an unwanted new entry
      VarSetCapacity(var2, 16, 0)
      DllCall(VTable(pdic,9), UInt,pdic, UInt,&var1, UInt,&var2)
      pItm := *(&var2+8) | *(&var2+9) << 8 | *(&var2+10) << 16 | *(&var2+11) << 24
      Unicode2Ansi(pItm, sItm)
      DllCall("oleaut32\SysFreeString", UInt,pItm)
   }
   DllCall("oleaut32\SysFreeString", UInt,pKey)
   Return sItm
}

Count(pdic) {      ; #entries
   DllCall(VTable(pdic,11), UInt,pdic, IntP,nCount)
   Return nCount
}

Exists(pdic, sKey) {
   AllocBString(pKey, var, sKey)
   DllCall(VTable(pdic,12), UInt,pdic, UInt,&var, IntP,bExist)
   DllCall("oleaut32\SysFreeString", UInt,pKey)
   Return bExist
}

Rename(pdic, sKeyFr, sKeyTo) {
   AllocBString(pKeyFr, var1, sKeyFr)
   AllocBString(pKeyTo, var2, sKeyTo)
   DllCall(VTable(pdic,14), UInt,pdic, UInt,&var1, UInt,&var2)
   DllCall("oleaut32\SysFreeString", UInt,pKeyFr)
   DllCall("oleaut32\SysFreeString", UInt,pKeyTo)
}

Remove(pdic, sKey) {
   AllocBString(pKey, var, sKey)
   DllCall(VTable(pdic,16), UInt,pdic, UInt,&var)
   DllCall("oleaut32\SysFreeString", UInt,pKey)
}

RemoveAll(pdic) {
   Return DllCall(VTable(pdic,17), UInt,pdic)
}

GetCompareMode(pdic) {
   DllCall(VTable(pdic,19), UInt,pdic, IntP,nCompMode)
   Return nCompMode
}

HashVal(pdic,sKey) {
   AllocBString(pKey, var1, sKey)
   DllCall(VTable(pdic,12), UInt,pdic, UInt,&var1, IntP,bExist)
   VarSetCapacity(var2, 16, 0)
   DllCall(VTable(pdic,21), UInt,pdic, UInt,&var1, UInt,&var2)
   nHashVal := *(&var2+8) | *(&var2+9) << 8 | *(&var2+10) << 16 | *(&var2+11) << 24
   DllCall("oleaut32\SysFreeString", UInt,pKey)
   Return nHashVal
}

NextKey(ByRef penum, pdic="") {                        ; penum = "": create new list. pdic=0,"Clear": destroy list
   If penum =                                          ; not static: allow multiple independent lists
      DllCall(VTable(pdic,20), UInt,pdic, UIntP,penum) ; create key-list in penum
   VarSetCapacity(var, 16, 0)
   If (InStr("Clear0",pdic) || DllCall(VTable(penum,3), UInt,penum, UInt,1, UInt,&var, UInt,0)) {
      DllCall(VTable(penum,2), UInt,penum)             ; END: destroy key-list
      penum =                                          ; signal end of list
      Return                                           ; empty
   }
   pKey := [email protected](&var + 8)
   Unicode2Ansi(pKey, sKey)
   DllCall("oleaut32\SysFreeString", UInt,pKey)
   Return skey
}

AllocBString(ByRef Key, ByRef Var, sString) {
   Ansi2Unicode(sString, wString)
   Key := DllCall("oleaut32\SysAllocString", Str,wString)
   VarSetCapacity(Var, 16, 0)
   DllCall("ntdll\RtlFillMemoryUlong", UInt,&Var,  UInt,4, UInt,8)
   DllCall("ntdll\RtlFillMemoryUlong", UInt,&Var+8,UInt,4, UInt,Key)
}

[email protected](ptr) {
   Return *ptr | *(ptr+1) << 8 | *(ptr+2) << 16 | *(ptr+3) << 24
}

;;; COM Helper functions

CreateObject(ByRef CLSID, ByRef IID, CLSCTX = 5) {
   If StrLen(CLSID) = 38
      GUID4String(CLSID, CLSID)
   If StrLen(IID) = 38
      GUID4String(IID, IID)
   DllCall("ole32\CoCreateInstance", Str,CLSID, UInt,0, UInt,CLSCTX, Str,IID, UIntP,ppv)
   Return ppv
}
GUID4String(Byref CLSID, sString) {
   VarSetCapacity(CLSID, 16)
   Ansi2Unicode(sString, wString, 38)
   DllCall("ole32\CLSIDFromString", Str,wString, Str,CLSID)
}
Ansi2Unicode(ByRef sString, ByRef wString, nLen = 0) {
   If !nLen
      nLen := DllCall("MultiByteToWideChar", UInt,0, UInt,0, UInt,&sString, Int,-1, UInt,0, Int,0)
   VarSetCapacity(wString, nLen*2 + 1)
   DllCall("MultiByteToWideChar", UInt,0, UInt,0, UInt,&sString, Int,-1, UInt,&wString, Int,nLen)
}
VTable(ppv, idx) {
   Return [email protected]([email protected](ppv) + idx*4)
}
Unicode2Ansi(ByRef wString, ByRef sString, nLen = 0) {
   pString := wString + 0 > 65535 ? wString : &wString
   If !nLen
      nLen := DllCall("WideCharToMultiByte", UInt,0, UInt,0, UInt,pString, Int,-1, UInt,0, Int,0, UInt,0, UInt,0)
   VarSetCapacity(sString, nLen)
   DllCall("WideCharToMultiByte", UInt,0, UInt,0, UInt,pString, Int,-1, Str,sString, Int,nLen, UInt,0, UInt,0)
}
It looks like dictionaries, key lists are created in the COM memory space, which is destroyed when the script exits. Accordingly, crashed scripts or scripts terminated without explicitly destroying these data structures do not cause memory leaks. I tested it with XP SP2. If you experience problems, please post it here!

Edit 20070427: Changed function name AllocString to AllocBString
Edit 20070512: moved SetCompareMode into CreateDictionary, and added option to the NextKey function: if its 2nd parameter is 0 or a substring of "Clear", the penum list will be destroyed. (Thanks Toralf.)

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

I edited a little Sean's great script: changed some function names, added more test cases, replaced the enumerate function with NextKey (which is of lower level, providing more flexibility) removed the safe array functions (which don't work for me) and copied over the used COM helper functions, so there will be no dependency from another file. (If your project uses other COM functions, you may prefer the original #include.) This script is still Sean's, only written in another style:

Nice! I have a suggestion, though.
Would you retain the name SysAllocString?
I once got complaint from foom that I used different names from the API.
And, it's not mere allocation. The important property about it is that the string is owned by the system so that it can cross the process boundary.

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Laszlo, thanks for the refinements.
Sean, to the extent you agree with Laszlo's changes, maybe you'd consider applying them to the topmost post.

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005

Would you retain the name SysAllocString?
I once got complaint from foom that I used different names from the API.
And, it's not mere allocation. The important property about it is that the string is owned by the system so that it can cross the process boundary.

AllocString() does more than SysAllocString() in CoHelper.ahk (fill memory). The AllocString name was meant to avoid conflicts with the original. It does allocate a string now, so foom might accept this name for a different functionality.