Updated 2008-11-06 11:59:55 by LV

Okay, so someone stopped by the office recently and wanted to write a little application that would display buttons and, on press of the button, would launch a .bat file.

Sure, thought I. That sounds trivial.

So, first thing I did was having him install ActiveTcl on his machine. So far, so good.

Next, I give him a copy of John O's intro to tcl and tk. Off the smart fellow goes, getting his feet wet on Tcl.

Then, he returns. I'm still stuck, says he. I'm not seeing things work the way I expected.

So, thinks I, let's see what we can do.

First thing's first - click right on the desktop, create a new text document. Set the name to "launchbat.tk". Windows asks me if I'm sure that's what I want to do... changing the extension could render the file unusable. Since the file was empty, I think "I'm safe here". And what do you know - a feature icon appears!

Next, I have to get open an editor...

So I open with Notepad (sure wish there was an editor that came in ActiveTcl...).

Then I type:
 pack [button .exit -text Exit -command {return 0}]

and save the file.

I double click the feature, and up comes my button. I press the Exit button... and nothing happens.

So, I change that return to exit, save and run. This time, the button does what I expect.

Next, I add a line before that:
  console show

and now I get the console.

Now, it is time for a button that invokes something. Let's start with a command window. Now my file looks like this:
 console show

 pack [button .exit -text Exit -command {exit 0}]

 pack [button .cmd -text {Command Window} -command {exec cmd.exe}]

I save, start the application, and there's two buttons. So I press the command window button - nothing. No error on the console, no window appearing.

Now, just a note, to those wondering why my code looks so stupid. I don't know much about Windows, other than having used it as a black box from which I launch Word or internet explorer or Outlook.

So, I poke around the wiki, and find start. I don't have winutils, twapi, ec. here - just ActiveTcl, and we don't have admin privs and don't time or patience to work through the red tape it would take to get them. So the tcl code listed on the start page will do.

By making use of that code, I now have cmd.exe starting. The script now says:
 console show

 proc start {filename} {
  eval exec [auto_execok start] "" [list [file nativename $filename]]
 }

 wm title . "My Menu"
 font create helvfont -family Helvetica -size 10 -weight normal -slant roman

 pack [button .exit -font helvfont -text Exit -bd 1 -foreground #444444 -width 8 -command {exit 0}]

 pack [button .cmd -font helvfont -text {Command Window} -command {start cmd.exe}]

(that font create and wm title were things the original developer had tried - I added them so that he would see that he was okay. The same thing goes for the foreground color... just trying to work with someone to help them understand.

Next up - to try a real bat file.

But of course, it can't be as simple as just doing the same thing as the command window - because we want to ask for some information and then pass it to the bat file...
 console show

 proc start {filename args} {
  eval exec [auto_execok start] "" [list [file nativename $filename] $args]
 }

 wm title . "My Menu"
 font create helvfont -family Helvetica -size 10 -weight normal -slant roman

 pack [label .l -text {PC Name}]
 pack [entry .pcin -textvariable pcname]

 pack [button .findit -font helvfont -text {Look up PC} -command {start findit.bat $pcname}]

 pack [button .cmd -font helvfont -text {Command Window} -command {start cmd.exe}]

 pack [button .exit -font helvfont -text Exit -bd 1 -foreground #444444 -width 8 -command {exit 0}]

LV Okay, so this worked okay, I guess. But of course, more was needed :smile:. The developer next wanted to pass more than one argument to his bat file. (And yes, of course I suggested just rewriting the bat file in Tcl. And you can see the result... I'm still learning about bat files and Tcl ...)

So imagine this:
 proc start {filename args} {
  eval exec [auto_execok start] "" [list [file nativename $filename] $args]
 }

 wm title . "My Menu"
 font create helvfont -family Helvetica -size 10 -weight normal -slant roman

 pack [label .l -text {PC Name}]
 pack [entry .pcin -textvariable pcname]

 pack [label .l -text {User Name}]
 pack [entry .userin -textvariable username]

 pack [button .findit -font helvfont -text {Look up PC} -command {start findit.bat $pcname $username}]

 pack [button .cmd -font helvfont -text {Command Window} -command {start cmd.exe}]

 pack [button .exit -font helvfont -text Exit -bd 1 -foreground #444444 -width 8 -command {exit 0}]

This didn't work. Why? Well, I'm not certain. For some reason, the multiple arguments are being seen by findit.bat as a single argument, despite the eval in the start proc.

Does anyone have any idea what is going wrong here?

From comp.lang.tcl, Alex suggests:
 proc start {filename args} {
  eval exec [auto_execok start] [list [file nativename $filename] ] $args
 }

which seems to work for me. And dangers here?

MG would say that this is correct. I'm not an expert on using 'eval exec ...', but you have to basically list all the arguments passed to quote them probably. But the $args variable is already a list of all the extra args passed to the proc, so if you run that through list again it's done twice, and they end up showing as a single argument. Alex's suggestion is, I believe, spot on.

LV 2007 June 07, Glenn Jackman explains a reason why the proc should really be:

My latest try:
 proc start {filename args} {
  eval exec [auto_execok start] [list [list ] [file nativename $filename] ] $args
 }

or, Glenn's try:
 proc start {filename args} {
  eval [concat [auto_execok start] [list "" [file nativename $filename] ] $args
 }

or on Tcl 8.5
 proc start {filename args} {
  eval [list {*}[auto_execok start] "" [file nativename $filename]  {*}$args]
 }

In this article [1], Glenn explains that if filename containined a space, start assumes that is the window title, and tries to use the args as the thing to start. Having the "" first would mean that start supposedly didn't see that.

Can someone explain whether there is a preference, or problem between "My latest try:" and "Glenn's try:"?

MG The concat is unnecessary in Glenn's latest one, as eval concats its args automatically. (It's also missing the 'exec', but that's just a typo, I think.) Otherwise the end result is the same (the string rep of "" and [list] are both an empty string), as far as I can see.

If memory serves, I've read before (I think on this wiki, somewhere) that empty-arg workaround is specific to one/some versions of Windows, and that it doesn't work correctly on others (95/98 against NT-based Win, I think). A better way might be to avoid having a space in the filename at all, by using something like
  proc start {filename args} {
    eval exec [auto_execok start] [list [file attributes $filename -shortname]] $args
  }

I don't use 8.5, but I think you could go with this
  proc start {filename args} {
    exec {*}[auto_execok start] [file attributes $filename -shortname] {*}$args
  }

to get the right thing - you shouldn't need the list calls because it's not going through eval. (The point of those list calls is to allow the eval to expand auto_execok's returned value without it expanding the filename into more than one argument. If you avoid the eval and expand auto_execok's return (and the $args list) with {*}, the filename will stay safely as one argument anyway.)

Not tested, though, I'm afraid - I don't have a batch file handy to test it with, and it's been a good 12 years since I last wrote one...

LV So, how would I wrap a solution for the exec quotes problem into the current variation of start?
  # For WinXP or newer? Some older Windows may need an empty argument before
  * the file attributes list...

  proc start {filename args} {
    eval exec [auto_execok start] [list [file attributes $filename -shortname]] $args
  }

LV 2008 Nov 03 So, this past Friday, a developer contacted me about just this issue . From an Windows XP "run ..." window, he types
C:\path\to\vend.exe -aHOSTNAME -c"Remote Control" -sSERVERNAME

and the vend.exe command starts up, and creates a "remote control" session between his machine and HOSTNAME, through SERVERNAME.

Now he wants to do this from within Tcl.

So he of course started out with exec C:/path/to/vend.exe -a$hostname -c"Remote Control" -sSERVERNAME

but it didn't work. I got him to try with the version of start right about this paragraph. There were still problems. So he changed things to
start C:/path/to/vend.exe -aHOSTNAME {-c"Remote Control"} -sSERVERNAME

and the application starts - but not correctly. It doesn't seem to be recognizing that the -c argument has both words of "Remote Control" with the quotes around them.

Anyone have any ideas?

See also: