ASNA Visual RPG for .NET and AVR Classic both have the OSEXEC operation code. OSEXEC lets you launch an executable or batch file in its own process. The command line to launch is specified with the CmdLine keyword. The string you provide for CmdLine can have a fully qualified reference to the executable or you can use OSEXEC's Directory keyword to specify the path of the executable.

For example, let's say you need to launch the custbal.exe in the c:\avr_apps folder. You can launch it like this:

OSEXEC CmdLine('c:\avr_apps\custbal.exe')

Or, like this:

OSEXEC CmdLine('custbal.exe') Directory('c:\avr_apps')

The Directory keyword can be specified with or without a trailing "". If the executable launched is in your Windows path you don't need to specify its directory in either keyword.

Watch for embedded blanks

Let's assume custbal.exe lets you pass it a customer number from the command line. You could pass a customer number to custbal.exe like this:

OSEXEC CmdLine("custbal.exe 3456") Directory("c:\avr_apps")

where 3456 is the customer number you want to pass.

In this case, the CmdLine keyword's value is pretty simple. However, when either the executable or the argument(s) have embedded blanks, things get a little more complex. For example, it might first seem that the following example would work for an executable with an embedded blank in its name:

OSEXEC CmdLine("cust bal.exe 3456") Directory("c:\avr_apps")

However, in this case, Windows command processor thinks you're trying to launch the executable cust with the arguments bal.exe and 3456. Great care must be taken when embedded blanks are in the mix.

See this link for an AVR for .NET program to show command line arguments entered and ways to fetch command line arguments.

Resolving problems with embedded blanks

Before using OSEXEC, try your command line with the Windows RUN command from a DOS command line. When you do that, you learn that this command line runs cust bal.exe with an argument:

"cust bal.exe" 3456 

If the second argument had blanks in it, it would also need to be in quotes:

"cust bal.exe" "an arg" 

The Windows command processor insists on double quotes here. Single quotes won't work. If the executable or argument doesn't have embedded blanks, you can still put the quotes around the value.

Using a literal string isn't generally how you'd provide the command line in a real app. You'd usually use variables. Let's consider how you could create the "cust bal.exe" "an arg" command line with variables.

DclFld CommandLine Type(*String) 
DclFld Executable Type(*String)
DclFld Argument Type(*String)

Executable = "cust bal.exe" 
Argument = "an arg" 

CommandLine = '"' + Exectuable + '" "' + 'an arg'"

OSEXEC CmdLine(CommandLine)

As you can see, keeping track of the single and double quotes necessary gets challenging quickly. Before we tackle making this rational with AVR for .NET, let's consider how to make it rational in AVR Classic (this method also works for AVR for .NET but it has other, better, options).

For the examples that follow, let's assume we have a command line that needs three arguments with embedded spaces. You've tested this command line in the Windows command prompt and know it works:

ConvertData.exe "David 1" "Stephen 3" "Graham 5"

We'll look first at an AVR Classic solution and then a couple of AVR for .NET solutions.

Masks to the rescue in AVR Classic

For creating strings for OSEXEC, don't use concatenation, use a mask and string replacement. AVR Classic doesn't have string replacement capabilities built-in, but its Misc String Control does provide a Replace method that works very well.

In this case, the MASK constant provides a masked string that will ultimately provide the necessary OSEXEC CmdLine keyword value needed.

DclConst  MASK Value('ConvertData.exe "{arg1}" "{arg2}" "{arg3}') 

DclFld CommandLine Type(*String) 
DclFld Arg0 Type(*String) 
DclFld Arg1 Type(*String) 
DclFld Arg1 Type(*String) 

Arg1 = "David 1"
Arg2 = "Stephen 3"
Arg3 = "Graham 5"

CommandLine = StringCtl.Replace(CommandLine, "{arg1}", Arg1) 
CommandLine = StringCtl.Replace(CommandLine, "{arg2}", Arg2) 
CommandLine = StringCtl.Replace(CommandLine, "{arg3}", Arg3) 

OSExec CmdLine(CommandLine)

Yes, this takes more code than string concatenation, but you get it right more quickly and it's much easier to maintain. Unfortunately, for AVR Classic you'll pretty much need a mask value for each number of arguments you need.

Because most of AVR Classic is one-based, the values above one-based. Beware the .NET version is zero-based (ie, the first argument is the zeroth argument, not the number 1 argument).

A masked solution for AVR for .NET - a basic approach

.NET's String class provides a feature that improves the technique above for AVR Classic.

Consider a command line that needs three arguments with embedded spaces. You've tested this command line in the Windows command prompt and know it works:

ConvertData.exe "David 1" "Stephen 3" "Graham 5"

DclConst  MASK Value('ConvertData.exe "{0}" "{1}" "{2}"') 

DclFld CommandLine Type(*String) 
DclFld Arg0 Type(*String) 
DclFld Arg1 Type(*String) 
DclFld Arg1 Type(*String) 

Arg0 = "David 1"
Arg1 = "Stephen 3"
Arg2 = "Graham 5"

CommandLine = String.Format(MASK, Arg0, Arg1, Arg2) 

OSExec CmdLine(CommandLine) 

The String class's static Format method provides token replacement. It takes as many arguments as necessary to replace the {{x}} placeholders where 'x' is the placeholder's zero-based ordinal position in the string. In the example above, the Arg0 value replaces the {0} placeholder, Arg1 value replaces the {1} placeholder, and so on. There must be the same number of placeholders and replacement values. The Format method starts counting replacement values at zero and doesn't allow for a more meaningful name than this numeric ordinal position of the value. The Format provides very powerful formatting features and is worth some serious study.

A masked solution for AVR for .NET - an advanced approach

While the .NET version above is a less codey than the AVR Classic version, it still requires different masks for a different number of arguments. I don't find that much of a limitation because I don't do a lot of these kinds of replacements, however, just for the exercise value, let's consider a more flexible solution. First, we need a general-purpose method that we'll call ReplaceMaskTokens:

BegFunc ReplaceMaskTokens Type(*String) 
    DclSrParm Tokens Type(System.Collections.Specialized.StringCollection) 

    DclFld sb Type(StringBuilder) New()
    ForEach Token Type(*String) Collection(Tokens) 
        sb.Append(String.Format('"{0}" ', Token)) 
    LeaveSr sb.ToString().Trim() 

This method uses the System.Collection.Specialized namespace's StringCollection and the System.String namespace's StringBuilder class to add flexibility. StringBuilder provides a buffer into which you can append strings and StringCollection lets you easily pass as many replacement values as necessary.

Call ReplaceMaskTokens like this:

DclFld CommandLine Type(*String) 
DclFld Tokens Type(StringCollection) New()    

Tokens.Add('David 1')
Tokens.Add('Stephen 3')
Tokens.Add('Graham 5') 

CommandLine = "ConvertData.exe " + ReplaceMaskTokens(Tokens)

OSExec CmdLine(CommandLine) 

You can add as many token values as necessary to the Tokens collection and ReplaceMaskTokens would always return the correct string value needed for OSEXEC.

Throw this code into a little test program and use the debugger to play around with it--especially if you aren't already familiar with the StringBuilder and StringCollection classes. There are lots of places where their power comes in handy.