|
One of the most frequently asked questions online goes along the lines of:
- How do I pass information (filenames) to my program?
Easy! Lookup the Command$() function in the help files that came with VB. Not long after doing this, many folks figure out that they can rather simply associate a given file extension with their application, by making just a few registry entries. When files with that extension are subsequently double-clicked, Windows passes their name(s) to the associated application. So the inevitable follow-up question becomes:
- How can I make sure all the files are opened by the same instance?
This is where the problem first becomes obvious. Windows fires off the requests one by one, starting a new instance of the application for each file to be opened. Somehow, you need to send the second and subsequent filenames over the first instance of your application, then quit the extraneous instance.
Almost a decade ago, I wrote up a method (see below) that involves subclassing and using the WM_COPYDATA message to pass data between instances of an application. This method still works, of course, and is especially useful in situations where an ongoing "conversation" needs to occur, or you don't have particular file extensions associated with your application. But WM_COPYDATA is extremely "heavy" for the most common, simple cases.
If you have associated one or more file extensions with your application, Windows offers a native method to accomplish your goal. While many view DDE as a relic that's outlived its usefulness (indeed, VS.NET doesn't even support it!), this is exactly the mechanism Windows uses to pass instructions to applications when their self-defined action verbs (open, print, etc.) are selected within the file system.
Defining Association Verbs
The easiest way to associate an extension, and define corresponding verbs, for an application is through use of a REG file. For example:
Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\.xyz] @="XYZfile" [HKEY_CLASSES_ROOT\XYZfile] @="Association Test File (XYZ)" [HKEY_CLASSES_ROOT\XYZfile\Shell] [HKEY_CLASSES_ROOT\XYZfile\Shell\Open] @="&Open" [HKEY_CLASSES_ROOT\XYZfile\Shell\Open\command] @="\"C:\\Code\\Samples\\PrevInst\\DDE\\PrevInstDDE.exe\" \"%1\"" [HKEY_CLASSES_ROOT\XYZfile\Shell\Open\ddeexec] @="[OPEN(%1)]" [HKEY_CLASSES_ROOT\XYZfile\Shell\Open\ddeexec\application] @="PrevInstDDE" [HKEY_CLASSES_ROOT\XYZfile\Shell\Open\ddeexec\topic] @="system"There's lots of documentation out there what each of these entries means, so I'll just touch on a few in the specific context of this sample. Of course, the above entries could (arguably should) be made with API registry calls, rather than relying on a user to Merge a REG file.
- ddeexec
- This is the actual command that's passed to your application, where %1 is replaced with the filename the user had selected. You can use multiple commands, each enclosed within square brackets, to cause a specific sequence to be sent.
- ddeexec\application
- The basename for your application - the part that comes before the ".exe". If you use the same name as your project, Windows will initiate the DDE conversation with instances running under the IDE as well.
- ddeexec\topic
- Corresponds to what VB calls a LinkTopic. You must assign this same value to a form or control that's always available while your application is running.
Listening For Your Cue
Once all the proper registry entries have been made, setting up your application to work together with Windows really couldn't be simpler. It really comes down to just these few lines of code in your main form:
Private Sub Form_LinkExecute(CmdStr As String, Cancel As Integer) Debug.Print CmdStr End Sub Private Sub Form_Load() ' LinkMode must be set at design time! 'Me.LinkMode = 1 - Source ' LinkTopic must match that stored in registry! Me.LinkTopic = "system" ' (Arbitrary) ' Just for kicks! If Len(Command$) Then MsgBox Command$, vbInformation, "Command$()" End If End SubYou'll find that, when the user double-clicks one of your data files, you no longer get anything directly on the command line if an instance of your application is already running. If the user selects multiple data files, and chooses one of your defined verbs, you will only get one file on the command line, while both this initial and all subsequent files will be passed via DDE.
Download this sample below, and play around a bit. It's so simple, the patterns should become immediately apparent.
This sample, or the one from which it originally derived, was published (or at least peripherally mentioned) in the following article(s):
- Out of Context, Programming Techniques, VBPJ, July 1996
- Handle Strings, Dates, and Colors, Ask the VB Pro, VPPJ, July 1998
This sample uses the following API calls:
Module Library Function FPrevInst.frm kernel32
user32lstrlen
lstrlen
RtlMoveMemory
FindWindow
IsIconic
SendMessage
SetForegroundWindow
ShowWindowMPrevInst.bas kernel32
user32lstrlen
RtlMoveMemory
CallWindowProc
FindWindow
GetWindowLong
IsIconic
SendMessage
SetForegroundWindow
SetProp
SetWindowLong
ShowWindowDon't see what you're looking for? Here's a complete API cross-reference.
Please, enjoy and learn from this sample. Include its code within your own projects, if you wish. But, in order to insure only the most recent code is available to all, I ask that you don't share the sample by any form of mass distribution. Download PrevInst.zip, 52Kb, Last Updated: Tuesday, February 21, 2006
The following resources may also be of interest: