AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

EnumResources

 
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Mon Nov 10, 2008 12:23 pm    Post subject: EnumResources Reply with quote

EnumResources( Filename, Type [, LabelOrFunctionName ] )
Enumerates names of resources of a given type in a given executable (exe, dll, icl, etc.) file.
Code:
; EnumResources( Filename, Type [, LabelOrFunctionName ] )
;   Requires AHK v1.0.47.06.

; Type:
;   See MSDN: Resource Types -
;       http://msdn.microsoft.com/en-us/library/ms648009(VS.85).aspx

EnumResources(Filename, Type, Label="")
{
    hmod := DllCall("GetModuleHandle", "str", Filename)
    ; If the DLL isn't already loaded, load it as a data file.
    loaded := !hmod
        && hmod := DllCall("LoadLibraryEx", "str", Filename, "uint", 0, "uint", 0x2)
   
    enumproc := RegisterCallback("EnumResources_callback","F")
    VarSetCapacity(param,8,0), NumPut(&Label, param)
    ; Enumerate the resources.
    DllCall("EnumResourceNames", "uint", hmod, "uint", Type, "uint", enumproc, "uint", &param)
    DllCall("GlobalFree", "uint", enumproc)
   
    ; If we loaded the DLL, free it now.
    if loaded
        DllCall("FreeLibrary", "uint", hmod)
   
    return NumGet(param,4)
}

EnumResources_callback(hModule, lpszType, lpszName, lParam)
{
    NumPut(1 + NumGet(lParam+4), lParam+4)
    if (lpszName >> 16 != 0)
        lpszName := DllCall("MulDiv","int",lpszName,"int",1,"int",1,"str")
    Label := DllCall("MulDiv","int",NumGet(lParam+0),"int",1,"int",1,"str")
    if Label !=
    {
        if IsLabel(Label)
        {   ; ErrorLevel = resource ID or name.
            ErrorLevel := lpszName
            gosub %Label%
        }
        else
            %Label%(lpszName)   ; Dynamic function call. Requires AHK v1.0.47.06.
    }
    return ErrorLevel!="#stop"
}


Examples:
Code:
NumIcons := EnumResources(A_AhkPath, 14, "EnumGroupIcons")
MsgBox % NumIcons " icons:`n`n" Icons
ExitApp

EnumGroupIcons:
    Icons .= ErrorLevel "`n"
return

Code:
NumIcons := EnumResources(A_AhkPath, 14, "EnumGroupIcons_callback")
MsgBox % NumIcons " icons:`n`n" Icons
ExitApp

EnumGroupIcons_callback(resource_name) {
    global Icons .= resource_name "`n"
}
Back to top
View user's profile Send private message Visit poster's website
Obi-Wahn



Joined: 20 Apr 2006
Posts: 75
Location: Vienna

PostPosted: Mon Nov 10, 2008 5:36 pm    Post subject: Reply with quote

Hi Lexikos!

Thanks for sharing the Function. I'm trying to understand the code, but I have no Idea what the RegisterCallback() is.
And even without this knowledge, would it be possible to merge these two functions together to one function (eg. with a return-given list or a list with byref). I think the usage of the function would be more clear...

Thanks for the Function
O-W
Back to top
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Tue Nov 11, 2008 8:16 am    Post subject: Reply with quote

Quote:
Source: AutoHotkey Documentation: RegisterCallback
Creates a machine-code address that when called, redirects the call to a function in the script.

As the callback function is not called directly from script, it would not be possible to pass a ByRef parameter. It allows only a single user-defined parameter containing a 32-bit integer. The script uses this to pass the address of a "structure" containing data relevant to the enumeration - i.e. the number of resources enumerated so far and a pointer to the LabelOrFunctionName string.

There are a few ways I can think of to build a list of resource names and return it to the caller:
  • Global or Static variables.
    • If a single variable is used, only one enumeration can be performed at a time. If a second enumeration is started via an interrupting thread (i.e. hotkey), it could "corrupt" the result of the first enumeration.
    • A dynamically-named variable could be used, based on an integer passed in the data structure. This would not have the above problem, but one would have to take care to reuse variables - without conflicting with any current enumerations - as a long-running script could otherwise consume more and more memory.
  • (Example 1) Memory could be allocated within the callback and returned via the data structure.
  • (Example 2) A variable reference (ByRef) may be passed via the data structure using LowLevel functions.
  • Pass a pointer to a structure or object capable of storing a list of values. For instance, A_Array or Scripting.Dictionary.

I'll work up an "example" or two.

(Edit)

Example 1 - use Global(Re)Alloc to allocate memory to store the list. A pointer to this memory is returned via the param structure. For simplicity, it reallocates the list for each item. Reallocation can be slow - a more efficient function would estimate the required space for the list and/or reallocate in larger increments to reduce the number of reallocations.
Code:
list := GetResourceNames(A_AhkPath, 14)
MsgBox % ErrorLevel " icons:`n`n" list

GetResourceNames_callback(hModule, lpszType, lpszName, lParam)
{
    ; Increment the counter for this enumeration.
    NumPut(1 + NumGet(lParam+0), lParam+0)
   
    ; lpszName may be an integer identifier or a pointer to a string.
    ; If it is a pointer to a string, retrieve the string.
    if (lpszName >> 16 != 0)
        lpszName := DllCall("MulDiv","int",lpszName,"int",1,"int",1,"str")
   
    ; Retrieve variables from the structure.
    listptr := NumGet(lParam+4)
    listlen := NumGet(lParam+8)
   
    if listptr
    {
        ; Reallocate the list to make room for the name of this resource.
        listptr := DllCall("GlobalReAlloc", "uint", listptr, "uint", listlen+StrLen(lpszName)+2, "uint", 2)
        ; Append delimiter.
        NumPut(Asc("`n"), listptr + listlen, "char")
        listlen += 1
    }
    else
    {
        ; Allocate memory for the first resource name.
        listptr := DllCall("GlobalAlloc", "uint", 0, "uint", StrLen(lpszName)+1)
    }
   
    ; Append the name of this resource onto the list.
    DllCall("RtlMoveMemory", "uint", listptr + listlen, "str", lpszName, "uint", StrLen(lpszName)+1)
    listlen += StrLen(lpszName)
   
    ; Update the structure.
    NumPut(listptr, lParam+4)
    NumPut(listlen, lParam+8)
   
    ; Continue enumeration.
    return true
}

GetResourceNames(Filename, Type)
{
    ; Get a handle to a loaded DLL.
    hmod := DllCall("GetModuleHandle", "str", Filename)
    ; If the DLL isn't already loaded, load it as a data file.
    loaded := !hmod
        && hmod := DllCall("LoadLibraryEx", "str", Filename, "uint", 0, "uint", 0x2)
   
    ; Create a callback to be called by EnumResourceNames().
    enumproc := RegisterCallback("GetResourceNames_callback","F")
    ; Create a structure to pass to the callback.
    VarSetCapacity(param,12,0)
    ; Enumerate the resources.
    DllCall("EnumResourceNames", "uint", hmod, "uint", Type, "uint", enumproc, "uint", &param)
    ; Free the memory used by the callback.
    DllCall("GlobalFree", "uint", enumproc)
   
    ; If we loaded the DLL, free it now.
    if loaded
        DllCall("FreeLibrary", "uint", hmod)
   
    ; Retrieve the list of resource names.
    if listptr := NumGet(param,4)
    {
        list := DllCall("MulDiv","int",listptr,"int",1,"int",1,"str")
        DllCall("GlobalFree", "uint", listptr)
    }
    ; Else: No resources or an error occurred. Leave list blank.
   
    ; Retrieve the number of resources enumerated.
    ErrorLevel := NumGet(param)
   
    return list
}


Example 2 - use LowLevel functions to allow the callback to refer to the main function's local variables. AutoHotkey handles memory allocation for the variables - typically more efficiently than example 1.
Code:
list := GetResourceNames(A_AhkPath, 14)
MsgBox % ErrorLevel " icons:`n`n" list

GetResourceNames_callback(hModule, lpszType, lpszName, lParam)
{
    ; lpszName may be an integer identifier or a pointer to a string.
    ; If it is a pointer to a string, retrieve the string.
    if (lpszName >> 16 != 0)
        lpszName := DllCall("MulDiv","int",lpszName,"int",1,"int",1,"str")
   
    ; Retrieve variable references from the structure.
    ; list and count become our local aliases for list and count in GetResourceNames().
    __alias(list, NumGet(lParam+0)), __alias(count, NumGet(lParam+4))
   
    ; Append the name of this resource to the list.
    list .= (count ? "`n" : "") . lpszName
    count += 1
   
    ; Continue enumeration.
    return true
}

GetResourceNames(Filename, Type)
{
    ; Requires LowLevel.ahk - http://www.autohotkey.com/forum/topic26300.html
    LowLevel_init()
   
    ; Get a handle to a loaded DLL.
    hmod := DllCall("GetModuleHandle", "str", Filename)
    ; If the DLL isn't already loaded, load it as a data file.
    loaded := !hmod
        && hmod := DllCall("LoadLibraryEx", "str", Filename, "uint", 0, "uint", 0x2)
   
    ; Create a callback to be called by EnumResourceNames().
    enumproc := RegisterCallback("GetResourceNames_callback","F")
    ; Create a structure to pass to the callback.
    VarSetCapacity(param,8,0)
    ; Store low-level variable references in the structure.
    NumPut(__getVar(count), NumPut(__getVar(list), param))
    ; Enumerate the resources.
    DllCall("EnumResourceNames", "uint", hmod, "uint", Type, "uint", enumproc, "uint", &param)
    ; Free the memory used by the callback.
    DllCall("GlobalFree", "uint", enumproc)
   
    ; If we loaded the DLL, free it now.
    if loaded
        DllCall("FreeLibrary", "uint", hmod)
   
    ; list and count are assigned by the callback function.
    ErrorLevel := count
    return list
}
Back to top
View user's profile Send private message Visit poster's website
Obi-Wahn



Joined: 20 Apr 2006
Posts: 75
Location: Vienna

PostPosted: Tue Nov 11, 2008 1:59 pm    Post subject: Reply with quote

Hi!

Thanks Lexikos, thats quite exactly the way I like it.
I've 2 (finally) Questions:

1.) In the first post, you link to the MSDN-Site, which contains a list of resource types that can be queried. But at the MSDN Site, the Types are Names (like RT_BITMAP), but the script doesn't accept this style. It seems like, you have to pass a Number as equivalent of the Style-Name. But there are no numbers on the MSDN Site. Does I have to guess the numbers, or exists a List of Numbers? Even with google, I found only lists with the Names, not numbers.

2.) Thats a What-If question. What if the RegisterCallback points on the same function as launched from? The callpack function has 4 parameters (which all needed, i guess). Woud it be possible to make a if-question (like if (lParam != "") ) and execute the code from there?
Should be the same, or am I wrong?

Thanks
O-W

EDIT: I tried to pack it into one function, an the RegisterCallback calls the function 7 times (using resource A_AhkPath) with Icon-Type. BUT: There are no Names of the resource.
Code:
MsgBox % "Icon Names:`n" GetResNames(A_AhkPath, 14)
;MsgBox % "Icon Names:`n" GetResourceNames(A_AhkPath, 14, C) "`n`n" C " Icons found"
ExitApp

GetResNames(File, Type, Count="", lParam="") {
   If (File = "0") {
      NumPut(1 + NumGet(lParam+0), lParam+0)
      If (Count >> 16 != 0)
         Count := DllCall("MulDiv", Int, Count, Int, 1, Int, 1, Str)
      listptr := NumGet(lParam+4), listlen := NumGet(lParam+8)
      If listptr {
         listptr := DllCall("GlobalReAlloc", UInt, listptr, UInt, listlen+StrLen(Count)+2, UInt, 2)
         NumPut(Asc("`n"), listptr + listlen, "char"), listlen += 1
      } Else
         listptr := DllCall("GlobalAlloc", UInt, 0, UInt, StrLen(Count)+1)
      DllCall("RtlMoveMemory", UInt, listptr + listlen, Str, Count, UInt, StrLen(Count)+1)
      listlen += StrLen(Count)
      NumPut(listptr, lParam+4), NumPut(listlen, lParam+8)
      ;Msgbox, , , here, 1   ; Simple debug, msgbox appears 7 times
      Return, true
   }
   If (!hmod := DllCall("GetModuleHandle", Str, File))
      hmod := DllCall("LoadLibraryEx", Str, File, UInt, 0, UInt, 0x2), loaded := 1
   enumproc := RegisterCallback("GetResNames", "F")
   VarSetCapacity(param, 12, 0)
   DllCall("EnumResourceNames", UInt, hmod, UInt, Type, UInt, enumproc, UInt, &param)
   DllCall("GlobalFree", UInt, enumproc)
   IfEqual, loaded, 1, DllCall("FreeLibrary", UInt, hmod)
   If (listptr := NumGet(param,4)) {
      list := DllCall("MulDiv", Int, listptr, Int, 1, Int, 1, Str)
      DllCall("GlobalFree", UInt, listptr)
   }
   Count := NumGet(param)
   Return, list
}

GetResourceNames(Filename, Type, ByRef Count="") {
   ; Get a handle to a loaded DLL.
   If (!hmod := DllCall("GetModuleHandle", Str, File))
      hmod := DllCall("LoadLibraryEx", Str, Filename, UInt, 0, UInt, 0x2)
   ; Create a callback to be called by EnumResourceNames().
   enumproc := RegisterCallback("GetResourceNames_callback", "F")
   ; Create a structure to pass to the callback.
   VarSetCapacity(param, 12, 0)
   ; Enumerate the resources.
   DllCall("EnumResourceNames", UInt, hmod, UInt, Type, UInt, enumproc, UInt, &param)
   ; Free the memory used by the callback.
   DllCall("GlobalFree", UInt, enumproc)
   ; If we loaded the DLL, free it now.
   If loaded
      DllCall("FreeLibrary", UInt, hmod)
   ; Retrieve the list of resource names.
   If (listptr := NumGet(param,4)) {
      list := DllCall("MulDiv", Int, listptr, Int, 1, Int, 1, Str)
      DllCall("GlobalFree", UInt, listptr)
   } ; Else: No resources or an error occurred. Leave list blank.
   ; Retrieve the number of resources enumerated.
   Count := NumGet(param)
   Return, list
}

GetResourceNames_callback(hModule, lpszType, lpszName, lParam) {
   ; Increment the counter for this enumeration.
   NumPut(1 + NumGet(lParam+0), lParam+0)
   ; lpszName may be an integer identifier or a pointer to a string.
   ; If it is a pointer to a string, retrieve the string.
   If (lpszName >> 16 != 0)
      lpszName := DllCall("MulDiv", Int, lpszName, Int, 1, Int, 1,Str)
   ; Retrieve variables from the structure.
   listptr := NumGet(lParam+4), listlen := NumGet(lParam+8)
   If listptr {
      ; Reallocate the list to make room for the name of this resource.
      listptr := DllCall("GlobalReAlloc", UInt, listptr, UInt, listlen+StrLen(lpszName)+2, UInt, 2)
      ; Append delimiter.
      NumPut(Asc("`n"), listptr + listlen, "char"), listlen += 1
   } Else {
      ; Allocate memory for the first resource name.
      listptr := DllCall("GlobalAlloc", UInt, 0, UInt, StrLen(lpszName)+1)
   }
   ; Append the name of this resource onto the list.
   DllCall("RtlMoveMemory", UInt, listptr + listlen, Str, lpszName, UInt, StrLen(lpszName)+1)
   listlen += StrLen(lpszName)
   ; Update the structure.
   NumPut(listptr, lParam+4), NumPut(listlen, lParam+8)
   ; Continue enumeration.
   Return, true
}


@lexikos: I modified the functions a little bit. I hope, its ok.
Back to top
View user's profile Send private message Visit poster's website
freakkk



Joined: 29 Jul 2005
Posts: 179

PostPosted: Tue Nov 11, 2008 7:24 pm    Post subject: Reply with quote

Obi-Wahn wrote:
1.) In the first post, you link to the MSDN-Site, which contains a list of resource types that can be queried. But at the MSDN Site, the Types are Names (like RT_BITMAP), but the script doesn't accept this style. It seems like, you have to pass a Number as equivalent of the Style-Name. But there are no numbers on the MSDN Site. Does I have to guess the numbers, or exists a List of Numbers? Even with google, I found only lists with the Names, not numbers.
You can look them up using ApiVeiwer --or-- SKAN's Crazy Scripting : A handy tool to lookup Win32 Constants. Also-- here is a thread that can probably help answer any general questions you have on getting started w/ dllcalls.

Obi-Wahn wrote:
2.) Thats a What-If question. What if the RegisterCallback points on the same function as launched from? The callpack function has 4 parameters (which all needed, i guess). Woud it be possible to make a if-question (like if (lParam != "") ) and execute the code from there? Should be the same, or am I wrong?

You can call a callback adr (recursion like), but the problem your running into is that your func cannot have byref params (like Lexikos mentioned), nor can it have default values. Here is an example that should illustrate that for you:
Code:
example1( 1000 )
example2( 1000 )
exitapp

example1( p ) {
  If ( A_EventInfo != 0 )
    MsgBox,, % A_ThisFunc, Input from calling your callback adr:  %p%

  If ( A_EventInfo = 0 )  {
    MsgBox,, % A_ThisFunc, Input from you:  %p%
    adr := RegisterCallback( A_ThisFunc, "F")
    r := DllCall(adr, "UInt", p )
    DllCall("GlobalFree", "UInt",adr)
  }
}

example2( p="default string" ) {
  If ( A_EventInfo != 0 )
    MsgBox,, % A_ThisFunc, Input from calling your callback adr:  %p%

  If ( A_EventInfo = 0 )  {
    MsgBox,, % A_ThisFunc, Input from you: %p%
    adr := RegisterCallback( A_ThisFunc, "F")
    r := DllCall(adr, "UInt", p )
    DllCall("GlobalFree", "UInt",adr)
  }
}

Notice how the dllcall invoking your callback address ('<==') is identical, but the param doesn't work the same when callback is called in example2.

It just so happens that for EnumResource, you could get away with combining those into a single function, but thats only because we could get away w/ not passing param & instead store the label in a static var.
Code:

NumIcons := EnumResources(A_AhkPath, 14, "EnumGroupIcons")
MsgBox % NumIcons " icons:`n`n" Icons
exitapp

EnumGroupIcons:
    Icons .= ErrorLevel "`n"
return

EnumResources(Filename, Type, Label)
{
  static uLabel
 
  If ( A_EventInfo = 0 )  { ; when called by user
    hmod := DllCall("GetModuleHandle", "str", Filename)
    ; If the DLL isn't already loaded, load it as a data file.
    loaded := !hmod
        && hmod := DllCall("LoadLibraryEx", "str", Filename, "uint", 0, "uint", 0x2)

    enumproc := RegisterCallback(A_ThisFunc,"F")
   
;--- not needed since static uLabel var will store the label user specified ---
;     VarSetCapacity(param,8,0)   ;, NumPut(&Label, param)
;     DllCall("EnumResourceNames", "uint", hmod, "uint", Type, "uint", enumproc, "uint", &param)
    uLabel := Label

    ; Enumerate the resources.
    DllCall("EnumResourceNames", "uint", hmod, "uint", Type, "uint", enumproc)
    DllCall("GlobalFree", "uint", enumproc)

    ; If we loaded the DLL, free it now.
    if loaded
        DllCall("FreeLibrary", "uint", hmod)

    uLabel := ""
    return NumGet(param,4)
  }
 
  If ( A_EventInfo != 0 )  {  ; when called from invoking callback
    ; Alias for Lex's EnumResources_callback param
    lpszName:=Label

;--- not needed anymore, since static uLabel var is storing the label user specified ---
;     NumPut(1 + NumGet(lParam+4), lParam+4)
;     if (lpszName >> 16 != 0)
;         lpszName := DllCall("MulDiv","int",lpszName,"int",1,"int",1,"str")
;     Label := DllCall("MulDiv","int",NumGet(lParam+0),"int",1,"int",1,"str")

    if uLabel !=
    {
        if IsLabel(uLabel)
        {   ; ErrorLevel = resource ID or name.
            ErrorLevel := lpszName
            gosub %uLabel%
        }
        else
            %uLabel%(lpszName)   ; Dynamic function call. Requires AHK v1.0.47.06.
    }
    return ErrorLevel!="#stop"
  }
}

I have to ask WHY? This doesn't offer any advantage, & its not really easier to follow. You also obviously loose the ability to offer the user optional parameters in the function call too.
_________________
.o0[ corey ]0o.
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Tue Nov 11, 2008 9:46 pm    Post subject: Reply with quote

Quote:
Source: AutoHotkey Documentation: RegisterCallback
ParamCount
The number of parameters that Address's caller will pass to it. If entirely omitted, it defaults to the number of mandatory parameters in the definition of FunctionName. In either case, ensure that the caller passes exactly this number of parameters.
Back to top
View user's profile Send private message Visit poster's website
freakkk



Joined: 29 Jul 2005
Posts: 179

PostPosted: Tue Nov 11, 2008 10:14 pm    Post subject: Reply with quote

Lexikos wrote:
Quote:
Source: AutoHotkey Documentation: RegisterCallback
ParamCount
The number of parameters that Address's caller will pass to it. If entirely omitted, it defaults to the number of mandatory parameters in the definition of FunctionName. In either case, ensure that the caller passes exactly this number of parameters.
oh-- haha. I actually meant, I have to ask WHY obi-wahn is fixated on combining your two functions into one; I actually prefer your original demo. Very Happy
_________________
.o0[ corey ]0o.
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Wed Nov 12, 2008 5:14 am    Post subject: Reply with quote

I know what you meant, and I agree. I was merely pointing out that optional parameters do not matter as long as you specify the actual number of parameters required by the callback.
Back to top
View user's profile Send private message Visit poster's website
Obi-Wahn



Joined: 20 Apr 2006
Posts: 75
Location: Vienna

PostPosted: Wed Nov 12, 2008 8:08 am    Post subject: Reply with quote

freakkk wrote:
...oh-- haha. I actually meant, I have to ask WHY obi-wahn is fixated on combining your two functions into one; I actually prefer your original demo. Very Happy

Hi Lexikos & freakkk!
I don't know why I'm fixated on functions, especially functions that are ONE function that does the job. Maybe it's like the "Lord of the Ring" - Thing. One Function to rule 'em all... Laughing
But seriously: I use functions that referer to other Functions but only if
a.) The Called function has more than one line code, and b.) It's called repeatedly.

BTW: I know, that ByRef Parameter are prohibited, but according to the helpfile, optional parameters are permitted.

Anyway. Thanks for the Help. I think I can live with two Functions....
Back to top
View user's profile Send private message Visit poster's website
Lexikos



Joined: 17 Oct 2006
Posts: 7295
Location: Australia

PostPosted: Wed Nov 12, 2008 8:36 am    Post subject: Reply with quote

Obi-Wahn wrote:
But seriously: I use functions that referer to other Functions but only if
a.) The Called function has more than one line code, and b.) It's called repeatedly.
Both are true of GetResourceNames...
Quote:
BTW: I know, that ByRef Parameter are prohibited, but according to the helpfile, optional parameters are permitted.
If you don't specify the number of parameters, optional parameters are excluded from the callback. Most callbacks use the stdcall calling convention, which requires the callee to "clean up" the stack. If the callback expects no parameters, any data pushed onto the stack by the caller will remain there after the callback returns. This can and will lead to stack corruption.
Back to top
View user's profile Send private message Visit poster's website
Display posts from previous:   
Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group