The purpose of this script is to allow Explorer (or other DDE clients) to send commands to a running instance of a script. Why?
The Example
File type associations. Say you've written a tabbed document viewer in AutoHotkey. When you launch a document, you'll want it to open in a new tab of an existing viewer. There is at least one obvious solution:Start up another instance of the script,
have it send a message to the existing viewer to open the document,
then exit.The problem with this approach is that it must start one (temporary) instance of the script for each document that you launch. This might not be ideal if you need to open a bunch of documents at once.
DDE, the optimal solution: using DDE, you can have Explorer send a message to your script. Now you can start the script once for all documents.
DDE_InitServer() is all you - as an application programmer - need to be concerned with. DDE_Initiate(), DDE_Execute() and DDE_Terminate() handle the necessary DDE messages for you. DDE_Execute() will call a label of your choosing.
A working example script (requires DDE.ahk):
#NoEnv ; Initialize the DDE server. DDE_InitServer("AutoHotkey DDE Test", "Launch", "DDE_Command") ; Let the user know if command-line args were specified. n = 1 if 0 > 0 ; var '0' contains the number of args MsgBox % "args: " %n% ; access arg '1' indirectly via 'n' return ; When you double-click a file (or select the appropriate action from the ; context menu), assuming the file type is associated correctly the script will ; call this sub-routine. DDE_Command: ; For now just show what command was received. MsgBox %DDE_Cmd% return(See further down for the .reg file accompanying this example.)
The Function
This may be saved in your user function library (%A_MyDocuments%\AutoHotkey\Lib) as DDE.ahk - this allows the file to be auto-included from any script that calls DDE_InitServer(). Otherwise, you'll need to manually #include the file.
; DDE_InitServer( ApplicationName, TopicName, LabelName [, LeaveCriticalOn ] ) ; Sets up the script to receive DDE "execute" requests. This function should ; be called at the top of the script in order to support associating file ; types with scripts via DDE. (See the comment above "Critical 500".) ; ; ApplicationName: ; Identifies the application/DDE server. ; Windows XP: This is the "Application" field of the file type action dialog. ; ; TopicName: ; The topic of DDE "conversation". ; Windows XP: This is the "Topic" field of the file type action dialog. ; Since this field cannot be empty, TopicName must be specified. ; ; LabelName: ; The label to execute when WM_DDE_EXECUTE is received. ; DDE_Cmd will contain the command-line to execute. ; In Windows XP, this is one of the following fields, depending on whether ; the script was running when the relevant action was executed: ; DDE Message ; DDE Application Not Running ; (If the latter is not specified, the former is used in its stead.) ; ; LeaveCriticalOn: ; Since "Critical" must be used for reliability, this function also disables ; Critical before returning. If the script must perform other actions that ; require Critical to be enabled, specify true for LeaveCriticalOn. ; ; Windows XP notes may also apply to earlier versions of Windows. ; DDE_InitServer(ApplicationName, TopicName, LabelName, LeaveCriticalOn=false) { global ; ; The DDE server MUST be ready to process DDE requests before the AutoHotkey ; window becomes "idle", otherwise launching a document will result in ; "Windows cannot find '<document name>'. ..." ; ; '500' ensures no messages are processed (thus the window appears non-idle) Critical 500 ; for at least the next 500 milliseconds. DDE_Application := ApplicationName DDE_Topic := TopicName DDE_Label := LabelName DetectHiddenWindows, On Process, Exist DDE_hwnd := WinExist("ahk_class AutoHotkey ahk_pid " ErrorLevel) DetectHiddenWindows, Off OnMessage(0x3E0, "DDE_OnInitiate") OnMessage(0x3E8, "DDE_OnExecute") OnMessage(0x3E1, "DDE_OnTerminate") if ! LeaveCriticalOn Critical Off } DDE_OnInitiate(wParam, lParam) ; handles WM_DDE_INITIATE { global DDE_Application, DDE_Topic, DDE_hwnd if DDE_GlobalGetAtomName(lParam&0xFFFF) != DDE_Application return if DDE_GlobalGetAtomName(lParam>>16) != DDE_Topic return if (atomApplication := DllCall("GlobalAddAtom","str",DDE_Application)) != 0 { if (atomTopic := DllCall("GlobalAddAtom","str",DDE_Topic)) != 0 { DetectHiddenWindows, On ; Send WM_DDE_ACK with correct application and topic names. SendMessage, 0x3E4, DDE_hwnd, atomApplication | atomTopic<<16,, ahk_id %wParam% DllCall("GlobalDeleteAtom","ushort",atomTopic) } DllCall("GlobalDeleteAtom","ushort",atomApplication) } } DDE_OnExecute(wParam, lParam) ; handles WM_DDE_EXECUTE { global DDE_hwnd, DDE_Label, DDE_Cmd if DllCall("UnpackDDElParam","uint",0x3E8,"uint",lParam,"uint*",0,"uint*",hCmd) { DDE_Cmd := DllCall("GlobalLock","uint",hCmd,"str"), DllCall("GlobalUnlock","uint",hCmd) gosub %DDE_Label% success := true } lParam := DllCall("ReuseDDElParam","uint",lParam,"uint",0x3E8 ,"uint",0x3E4,"uint",success ? 1<<15 : 0,"uint",hCmd) DetectHiddenWindows, On ; Acknowledge the request. (WM_DDE_ACK) PostMessage, 0x3E4, DDE_hwnd, lParam,, ahk_id %wParam% } DDE_OnTerminate(wParam) ; handles WM_DDE_TERMINATE { global DDE_hwnd DetectHiddenWindows, On ; Acknowledge WM_DDE_TERMINATE by posting another WM_DDE_TERMINATE. PostMessage, 0x3E1, DDE_hwnd, 0,, ahk_id %wParam% } DDE_GlobalGetAtomName(atom) { ; helper function if !atom return "" VarSetCapacity(name,255) ; maximum size of an atom string return DllCall("GlobalGetAtomName","ushort",atom,"str",name,"int",256) ? name : "" }Changes:[*:259ehb55]DDE_GlobalGetAtomName - Replaced arbitrary buffer size (1024) with maximum size of an atom string (255.)
Registering File Types
In order to have Explorer launch your script automatically - and send DDE messages to it - when you double-click the file or select an item from the context menu, you'll need to do a bit of work.
Windows XP
In Windows XP and earlier, this can be done without diving into the registry. Since I run only Windows XP and Vista, my instructions will be based on Windows XP. Some (perhaps most) of the steps may be relevant to older versions of Widnows. The steps are as follows:
[*:259ehb55]In an Explorer window, select Folder Options from the Tools menu.
[*:259ehb55]Select the File Types tab.
[*:259ehb55]Create a new file type or select an existing file type.
[*:259ehb55]To create a new file type, click New, type the File Extension you wish to associate with your script (for instance ahk or txt), then click OK.
[*:259ehb55]To select an existing file type, simply find its entry in the list and click it.[*:259ehb55]Click Advanced.
[*:259ehb55]If this is a new file type, you may want to enter a description (like "AutoHotkey Script") in the box at the top, or change the file type's icon (by clicking Change Icon.)
[*:259ehb55]Now decide what your action will be. For instance, it could be "open", "edit", "view", or whatever you want.
[*:259ehb55]If your action already exists, select it and click Edit.
[*:259ehb55]Otherwise, click New.If you are editing an existing action, you may want to skip some of the following steps.
[*:259ehb55]Type the name of the action (open, edit, etc.) into the Action field. Preceding a character with ampersand (&) will turn the character into a shortcut key for the action's entry in the context menu. (For instance, pressing "o" will execute "&Open".)
[*:259ehb55]Type the relevant path in the Application used to perform action field.
[*:259ehb55]For compiled (.exe) scripts, type the path of the script executable.
[*:259ehb55]For uncompiled (.ahk) scripts, type the path of AutoHotkey, followed by a space and the path of the script (enclosed in quote marks.)[*:259ehb55]Append any needed command-line arguments to the end of this field. If it does not include %1, XP will automatically append it. To support paths with spaces, %1 must be enclosed in quote marks. When the action is executed, %1 is automatically replaced with the path of the selected file.
[*:259ehb55]Ensure there is a check-mark next to Use DDE.
[*:259ehb55]The Application and Topic fields should be the same as the values you passed to DDE_InitServer() via the ApplicationName and TopicName parameters.
[*:259ehb55]Type a message/command in the DDE Message and DDE Application Not Running fields. If the script is not already running when you execute the action, Explorer will start the script and send (only) the DDE Application Not Running message. If you leave it empty, the DDE Message is sent in either case.
When either message is received, DDE.ahk calls (via gosub) the label/subroutine you passed to DDE_InitServer(), and the global variable DDE_Cmd is set to the value of the message. Again, %1 is automatically replaced with the path of the selected file.
[*:259ehb55]To finish, click OK. If you want the action to be performed when you double-click a file of the associated type, you may need to select the action and click Set Default. (However, the first action you create should automatically be the default.)[/list]These are the settings I used while testing the example script:
While MSDN recommends the message format: [command][command(parameters)] (one or more commands may be specified; parameter list is optional), any message format is likely to work. It is up to the application/script to parse the message.
Editing The Registry
At some point you may need to bypass the GUI method and go straight to the registry. For instance, you could write an installer for your application that writes the relevant registry keys using RegWrite. If you are using Windows Vista, you will need to edit the registry (using regedit or the RegWrite command), since Vista dumbs-down the file type association GUI.
I won't describe how to use RegWrite or regedit; however, I will describe the basic needed registry structure.
First, a couple of notes:
Each key in the registry can have a default value. In regedit, this is displayed as (Default). When writing or reading this value with RegWrite/RegRead, ValueName should be omitted or blank. In .reg files, default values are denoted as '@':
[path\to\registry\key] @="the key's default value"All of the following keys are contained in HKEY_CLASSES_ROOT. Bold text indicates a key name, while bold-italicized text indicates a key whose name varies.
.extension - for instance, .ahk
This key can either contain the keys as described below, or redirect to another key. If the keys are directly descended from this key, you may find oddities like having two "Open" actions.
(Default) - the name of the registry key to redirect to. The File Types GUI in Windows XP uses an automatic name like "ft000001".
keyname - for instance, ft000001 or ahkfile
The root key that contains the file type association settings. Its name depends on the (Default) value as described above.
(Default) - contains a description of the file type. For instance, "AutoHotkey Script".
[*:259ehb55]shell
(Default) - the name of the registry key containing the default (double-click) action. (This could be the name of the key below.)
[*:259ehb55]action_name - for instance open, view or edit.
A registry key representing an action in Explorer's context menu.
(Default) - the text to display for this action in Explorer's context menu. Prefix a character with ampersand (&) to turn the character into a shortcut key.
[*:259ehb55]command
(Default) - the path and command-line arguments of the application to execute. For instance, it could be the path to AutoHotkey followed by the path of an uncompiled script. It does not need to contain %1 (the file path), since this can be sent via DDE.
[*:259ehb55]ddeexec
(Default) - the message/command to send to the script. This should contain %1 which is automatically replaced with the path of the selected file.
[*:259ehb55]Application
(Default) - identifies the application/script. This must be the same as the ApplicationName you pass to DDE_InitServer().
[*:259ehb55]IfExec - optional.
(Default) - the message/command to send to the script if the script isn't already running. If the IfExec key doesn't exist or this value is empty/not set, the message/command specified in ddeexec\(Default) is used instead.
[*:259ehb55]Topic
(Default) - the topic of the DDE conversation. This must be the same as the TopicName you pass to DDE_InitServer().
[/list][/list][/list][/list]Example Registry File
Below is the result of exporting the relevant registry keys I used to test the script. I used the example script which can be found closer to the top of this post (Z:\dde.ahk), and tested with two files: test.testfile and Copy of test.testfile (both empty.)
Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\.testfile] @="ft000001" [HKEY_CLASSES_ROOT\ft000001] "EditFlags"=dword:00000000 "BrowserFlags"=dword:00000008 @="DDE Test File" [HKEY_CLASSES_ROOT\ft000001\shell] @="open" [HKEY_CLASSES_ROOT\ft000001\shell\open] @="&Open" [HKEY_CLASSES_ROOT\ft000001\shell\open\command] @="\"C:\\Program Files\\AutoHotkey\\AutoHotkey.exe\" \"Z:\\dde.ahk\" \"%1\"" [HKEY_CLASSES_ROOT\ft000001\shell\open\ddeexec] @="[open(\"%1\")]" [HKEY_CLASSES_ROOT\ft000001\shell\open\ddeexec\Application] @="AutoHotkey DDE Test" [HKEY_CLASSES_ROOT\ft000001\shell\open\ddeexec\IfExec] @="[open]" [HKEY_CLASSES_ROOT\ft000001\shell\open\ddeexec\Topic] @="Launch"