 |
AutoHotkey Community Let's help each other out
|
| View previous topic :: View next topic |
| Author |
Message |
Lexikos
Joined: 17 Oct 2006 Posts: 4367 Location: Qld, Australia
|
Posted: Thu Jan 08, 2009 8:52 am Post subject: |
|
|
| animeaime wrote: | | So, just to clarify, there is "no" (or little) performance loss by using the menu name (versus the menu handle) in your functions? | Already having the menu handle saves some extra work, so is "more efficient." I think the benefit may be negligible. I am trying to benchmark it now, but recreating the menu and setting the icons hundreds of times is causing problems. I think there may be a resource leak somewhere, other than the following which I caught immediately after using DllCallDebugger():
| Code: | if hbmColor
DllCall("DestroyObject","uint",hbmColor)
if hbmMask
DllCall("DestroyObject","uint",hbmMask)
| It should be DeleteObject. I must've mixed it up with DestroyIcon.
[
Edit: The other resource leak was in my benchmark script. MI_SetMenuItemIcon leaves it up to the caller to delete icons/bitmaps used before assigning another icon to the menu item or deleting the menu (item). My benchmark script was not doing this. Strangely, I did not see memory usage or handle count spike (watching task manager).
I think MI should take care of this, for a few reasons:
- Users are likely not to clean up the icons/bitmaps manually - myself included.
- Originally I chose not to delete the icons/bitmaps automatically on the premise that some other code might set an icon or bitmap, and would be expecting it not to be deleted. This is very unlikely.
- Unless it is running on Vista, MI already "reserves" the dwItemData field of each menu item. If some other code stores something in it other than an icon/bitmap, MI will "fail" either way.
Update pending.
]
| Quote: | | Lexikos wrote: | | Technically the menu handle does not change - one menu is deleted, and another is created with the same name. |
I'm not sure what you mean here. Does this mean that the other menu handles remain the same?
| Yes, the other menu handles remain the same.
When you remove all of the items from a menu, AutoHotkey deletes the menu. It no longer exists. If you then add items using the same menu name, AutoHotkey creates a new menu.
| Quote: | This is a very good idea - do you have a reference so I can figure out how to do that?
| There is only the AutoHotkey source code itself.
- Built-in functions are resolved by Script::FindFunc in script.cpp.
- The corresponding C++ functions (BIF_*) are defined in script2.cpp.
- Script::FindMenu in script_menu.cpp retrieves a pointer to a UserMenu given the name of an existing menu. This structure contains a handle to the menu.
It probably wouldn't take me long, but I have many other projects. I'm also not sure a built-in function would be the most appropriate way - just the easiest. |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Jan 08, 2009 10:55 am Post subject: |
|
|
Oh, sorry I misunderstood. I thought you meant built-in meaning built into my menu wrapper, not built into the AHK code. I was wondering how to program something to get the menu handles, and then I will include that function as part of my wrapper. If you'll allow, I would like to make an adaptation of your code. I belive, after briefly reading your code, that it can be done without the creation of a menu, but I'll test that tomorrow.
Edit: after reading the code my thoughts have turned to nope, it can't be done. You wrote a very neat getHandle function.
update: used the method described here to get the hwnd of the gui window. And fixed the position of the restore of the old detectHiddenWindows value
Update:
It seems you don't even have to show the GUI using this method.
| Code: | MI_GetMenuHandle(menu_name)
{
static h_menuDummy
If h_menuDummy=
{
Menu, menuDummy, Add
Menu, menuDummy, DeleteAll
Gui, 99:Menu, menuDummy
Gui, 99:+LastFoundExist
h_menuDummy := DllCall( "GetMenu", "uint", WinExist() )
If ErrorLevel or h_menuDummy=0
return 0
Gui, 99:Menu
Gui, 99:Destroy
}
Menu, menuDummy, Add, :%menu_name%
h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0 )
DllCall( "RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400 )
Menu, menuDummy, Delete, :%menu_name%
return h_menu
}
|
The reason I thought you could do it without the gui is all you need is a menu handle (which you can get, I believe, from a dllCall to createMenu. However, if it's not part of a window, you have to call destroyMenu (versus it being done automatically as part of a window) ) - my error  _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts.
Last edited by animeaime on Thu Jan 08, 2009 11:43 am; edited 1 time in total |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 4367 Location: Qld, Australia
|
Posted: Thu Jan 08, 2009 11:42 am Post subject: |
|
|
| animeaime wrote: | | You wrote a very neat getHandle function. | No. As the comments say, I adapted it from Shimanov's code. |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Thu Jan 08, 2009 11:52 am Post subject: |
|
|
Oh, right, sorry about that mistake.
I wanted to clarify what happens if the method returns 0. The static value value changes to something other than "", right? Wouldn't this mean that GUI 99 is never destroyed? I believe that's what happens, so I wanted to bring up that issue. Not sure what SHOULD happen, but thought I should bring that up. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 4367 Location: Qld, Australia
|
Posted: Thu Jan 08, 2009 12:50 pm Post subject: |
|
|
For ErrorLevel to be non-zero at that point, there must be a DllCall error - most are impossible or practically impossible in this case. For h_menuDummy to be zero, the window handle passed to GetMenu must be invalid, or the GUI has no Menu. It seems to me that if the script is able to fail in any of these ways, this tiny amount of error-checking will not save it.
Excluding errors that may be introduced by editing the code, that is.
Even if h_menuDummy is NULL and there is no error-checking, the final result will be that the function returns 0.
Bencharks:
I used my original tray menu icons example, Loop 1000. I ran two tests in various configurations - Handles and Names.
Using the owner-drawn methods, Names took 5% longer than Handles.
When the script runs on Vista without XP-compatibility mode, it creates 32-bit bitmaps to put in the menus. This allows Vista's menu styling to work, but is considerably slower. Using Vista's methods, Names took 0.5-2% longer than Handles.
Recreating the menu each iteration didn't have a significant impact on the results, even though MI_GetMenuHandle must be called each iteration instead of once, outside the loop.
Interestingly, the "Vista" tests ran almost twice as quickly on AutoHotkey pre-v1.0.48 beta than on v1.0.47.06. The "owner-draw" tests had only slight gain. |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Fri Jan 16, 2009 10:55 am Post subject: |
|
|
Bug Report (kinda):
If you try to get the menu handle for a menu that doesn't exist, it results in an Error on line 217 (where you add the "uncreated menu" to MenuDummy, "Menu, menuDummy, Add, :%menu_name%"). I'm about to post a question in the forum asking how to tell if a menu exists (with a given menu name), but I also wanted to make you aware of this fact. You could enable UseErrorLevel for the menu, but there doesn't seem a variable which stores the current setting. So, although you can turn it off, there is no way, that I know of, to see if the user had UseErrorLevel on to begin with.
Now you see why I say bug report, kinda. I'm not sure if there is a way to "fix" this, but I thought that you'd be the first one to be able to answer that. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 4367 Location: Qld, Australia
|
Posted: Fri Jan 16, 2009 6:16 pm Post subject: |
|
|
AutoHotkey throws up the error dialog when you try to access a menu that doesn't exist, why shouldn't MI? If you really want to pass around invalid menu names, use the following ugly hack:
| Code: | MI_GetMenuHandle(menu_name)
{
static h_menuDummy, p_try, WH_CALLWNDPROC:=0x4
If !h_menuDummy
{
Menu, menuDummy, Add
Menu, menuDummy, DeleteAll
Gui, 99:Menu, menuDummy
Gui, 99:+LastFound
h_menuDummy := DllCall("GetMenu","uint",WinExist())
Gui, 99:Menu
Gui, 99:Destroy
if !h_menuDummy
return 0
p_try := RegisterCallback("MI_GetMenuHandle_Try")
}
; Big ugly hack to catch error dialog.
VarSetCapacity(event_info, 4)
p := RegisterCallback("MI_GetMenuHandle_CallWndProcHook","F",3,&event_info)
h := DllCall("SetWindowsHookEx", "int", WH_CALLWNDPROC, "uint", p
, "uint", 0, "uint", DllCall("GetCurrentThreadId"))
NumPut(h, event_info)
r := DllCall(p_try,"str",menu_name)
DllCall("UnhookWindowsHookEx", "uint", h)
DllCall("GlobalFree","uint",p)
if !r
; Thread was exited prematurely or ErrorLevel was non-zero: menu does not exist.
return 0
h_menu := DllCall("GetSubMenu", "uint", h_menuDummy, "int", 0)
DllCall("RemoveMenu", "uint", h_menuDummy, "uint", 0, "uint", 0x400)
Menu, menuDummy, Delete, :%menu_name%
return h_menu
}
MI_GetMenuHandle_Try(p) {
Menu, menuDummy, Add, % ":" DllCall("MulDiv","int",p,"int",1,"int",1,"str")
if !ErrorLevel
return 1
}
MI_GetMenuHandle_CallWndProcHook(nCode, wParam, lParam)
{
static WM_INITDIALOG:=0x110
Critical
if (nCode<0x80000000 && NumGet(lParam+8)=WM_INITDIALOG)
DllCall("DestroyWindow","uint",NumGet(lParam+12))
else
return DllCall("CallNextHookEx", "uint", NumGet(A_EventInfo), "int", nCode, "int", wParam, "int", lParam)
} |
|
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Fri Jan 16, 2009 6:27 pm Post subject: |
|
|
... Never mind. I found a way around it. I just made a wrapper function to keep track of the UseErrorLevel and have it turn UseErrorLevel on/off as needed.
P.S. I forgot to say that I was sorry for being selfish, so I'll say it now, I'm sorry. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Mon Jan 19, 2009 7:50 am Post subject: |
|
|
Found a bug, and this one is actually a bug not my selfishness.
On line 220, it should be
| Code: | | h_menu := DllCall( "GetSubMenu", "uint", h_menuDummy, "int", 0, "uint" ) |
Currently, it returns an int, but it should return an unsigned int. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 4367 Location: Qld, Australia
|
Posted: Mon Jan 19, 2009 11:33 am Post subject: |
|
|
| Firstly, there is no missing comma in the current version of MI, v2.2. I uploaded it on the 8th, but looks like I forgot to point it out. Secondly, signed vs unsigned does not matter for handles. It can be more convenient to use signed, since -1 is shorter than the equivalent 0xFFFFFFFF - either indicating an invalid handle value. |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Mon Jan 19, 2009 11:58 am Post subject: |
|
|
The getSubMenu function returns Null (0), not -1 when invalid. Also, by returning signed menu handles, it causes problems if you wish to use those to index values later (because "-" is not valid in a variable name). This is the problem I encountered.
However, I do see your point of ease with functions that return -1 when invalid (such as GetMenuItemID) and having the return be -1 instead of 0xFFFFFFFF. So, my question becomes, will it cause a problem returning signed values, but having unsigned values as inputs to the dll calls (or will it convert)? If so, forget I said anything, and again sorry for the trouble. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 4367 Location: Qld, Australia
|
Posted: Mon Jan 19, 2009 1:53 pm Post subject: |
|
|
| Quote: | | So, my question becomes, will it cause a problem returning signed values, but having unsigned values as inputs to the dll calls (or will it convert)? | -1 and 0xffffffff, for example, have the same binary representation when put into a 32-bit signed or unsigned integer. Passing either to an "int" or "uint" parameter will have exactly the same result. The "u" only matters when AutoHotkey is retrieving a value and the script does more than simply pass the value on to DllCall or NumPut - for instance, return values, output parameters (int*, uintp, etc.) and NumGet.
| Quote: | | Also, by returning signed menu handles, it causes problems if you wish to use those to index values later (because "-" is not valid in a variable name). | I generally avoid using anything volatile as an "array index", as variables in AutoHotkey cannot be deleted - only their contents can be freed, and not even in all cases. It may only be a very small amount of memory each time, but it can add up in long-running scripts. |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Mon Jan 19, 2009 4:16 pm Post subject: |
|
|
Well, there are certain values that I want to associate with the menu item. Also, with the menu suite that I've been working on (and will post soon), that allows me to move an item around the menu (without changing the handle, and while retaining the "attributes" as well as the "goto label") menu handles aren't as volatile as they might otherwise be.
Also, then what would you recommend? I have several values that I wish to associate with a menu item, how would you link them? For example, I have a "type" and "value" attribute that I use to link to other data. For example, I'm working on a Bookmark manager and thus have many URLs that have data. I link the menu item handle to these "type" and "value" (e.g. URL 1, URL 2, etc.) which then link to the URL's name, address, tags, etc. I've gone through several rewrites of this structure, so if you have any suggestions, I'm all ears. _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
Lexikos
Joined: 17 Oct 2006 Posts: 4367 Location: Qld, Australia
|
Posted: Tue Jan 20, 2009 9:53 am Post subject: |
|
|
When owner-drawing is in use, MI uses the dwItemData field of the menu item to store a handle to the icon. This field is ideal for associating data with a menu item as it will remain valid even if the index of the menu item changes.
| MI.ahk wrote: | ; Associate the icon with the menu item. Relies on the probable case that no other
; script or dll will use dwItemData. If other scripts need to associate data with
; an item, MI should be expanded to allow it.
| If you are writing a wrapper for the menu APIs, you are in the ideal position to write functions for other scripts to associate arbitrary data with an item. Providing such functionality from a "menu icons" library does not seem appropriate. If you wish to integrate any or all of the functionality of MI.ahk, you have my approval.
Additionally, if everything goes through your wrapper, you can automatically release any resources associated with a menu item when it or its menu is deleted. I think the best way to do this would be to allow scripts to register a subroutine (label) or function to be called when an item is deleted. Your wrapper could then free the item's icon either explicitly, or implicitly via a cleanup subroutine or function.
Any feature to automatically release resources used by the menu when appropriate would be important for most scripters; even my own resident script leaked memory for a while because I'd forgotten to explicitly remove the icons before recreating the menu.
You should take a look at MMenu if you haven't already.
As for associating data with the menu itself, I would wrap the menu handle in a data structure. Within the menu API wrapper, the menu would be identified by a pointer to this structure rather than the menu handle. For example:
| Code: | mi := MenuInfo_new(0x539)
MenuInfo_setData(mi, "Greetings, forum.")
MsgBox % MenuInfo_getHandle(mi) ": " MenuInfo_getData(mi)
MenuInfo_delete(mi)
MenuInfo_new(handle) {
; Allocate menu info structure { uint handle, str data }.
mi := DllCall("GlobalAlloc", "uint", 0x40, "uint", mi_size:=8)
; Store handle.
NumPut(handle, mi+0)
return mi
}
MenuInfo_setData(mi, data) {
; Free previous data.
if NumGet(mi+4)
{
DllCall("GlobalFree", "uint", NumGet(mi+4))
; Store NULL pointer to represent empty string when data="",
; or in case error-handling is added and the function returns early.
NumPut(0, mi+4)
}
if data =
return
; Allocate buffer for string.
mi_data := DllCall("GlobalAlloc", "uint", 0x40, "uint", StrLen(data)+1)
; Copy string into buffer.
DllCall("lstrcpy", "uint", mi_data, "str", data)
; Store pointer to string.
NumPut(mi_data, mi+4)
}
MenuInfo_getData(mi) {
; MulDiv returns NumGet(mi+4) unmodified, but "str" causes AutoHotkey to
; interpret it as a string. Safe even if NumGet(mi+4) returns NULL (0).
return DllCall("MulDiv", "int", NumGet(mi+4), "int",1,"int",1, "str")
}
MenuInfo_getHandle(mi) {
return NumGet(mi+0)
}
MenuInfo_delete(mi) {
; It is important that setData() frees the previous data and does not
; allocate new memory when data="".
MenuInfo_setData(mi, "")
DllCall("GlobalFree", "uint", mi)
} | Robustness, performance, usability, flexibility, etc. could be improved, but this is just a brief example. Any number of "fields" can be added, either string or numeric, by adjusting mi_size in MenuInfo_new. dict.ahk uses a similar approach to implement an "associative array". ("dict" is short-hand for Dictionary.) |
|
| Back to top |
|
 |
animeaime
Joined: 04 Nov 2008 Posts: 1046
|
Posted: Tue Jan 20, 2009 10:45 am Post subject: |
|
|
That's genius!!! Thank you! I will definitely incorporate this and give propper thanks. I was wondering what that value was for...
That's awesome. Yeah, right now, insertions (both at the end and elsewhere), deletions, and moves (within the same menu, or not), are all done through function calls. Things such as Icons (provided by MenuIcons), Check/Uncheck, Enable/Disable, Default, and Standard/NoStandard can be done fine by using the AHK menu tools, so I don't intend to incorporate them into the wrapper.
It will be very easy to allow the user to specify the desired size and allow them to write whatever data they want. I never even knew about GlobalAlloc, so thank you for that. This will definitely make the code MUCH better. I was waiting to release it until i could figure a good system for this. Now, thanks to you I have one. After I sleep some, I'll start working on it, and should be able to post my wrapper within a day or so. I'm sure I'll have to figure out how to free the data so not to get memory leaks. Add in some testing and we're good.
Once again, THANK YOU! _________________ As always, if you have any further questions, don't hesitate to ask.
Add OOP to your scripts via the Class Library. Check out my scripts. |
|
| Back to top |
|
 |
|
|
You can post new topics in this forum You can reply to topics in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|