Often we need to wrap commandline utilities, rather than writing and exposing code natively through .NET or COM or however. I’m going to talk about my preferred design for wrapping these projects.
Let’s start with a simple commandline:
>>Z:\art\utilities\some_converter.exe -f triangle -q dxt5nm -width 1024 -height 1024 “C:\ emp\myFiles\*”
There are a few principles I abide by:
[ol]
[li]No options other than file paths should be strings.
[/li][li]So, options should be some value type: enum, integer, boolean, or even structs or classes. Just never arbitrary.
[/li][li]It should be much simpler to use your class than to call the commandline directly
[/li][li]So, make your property names very clear. You can use attributes or additional code to indicate what they should ‘write out to’ in the commandline.
[/li][li]A single method should ‘execute’ the class and commandline.
[/li][li]These types of classes should serialize into XML or a database- ie, they need some sort of GUID.
[/li][li]Why? So other objects can store references to them. Don’t always rely on a tool that does something a certain way- people should be able to associate your class with other objects and then execute that commandline whenever.
[/li][/ol]
An example use is wrapping a texture compression utility. Expose all the options in a class (or, perhaps, expose only some and supply default values for others- a subclass can have ALL options exposed if the person using your code requires additional options), allow the user to configure them and save settings (I’d advise against subclassing for presets- the thought has crossed my mind). You can then use it how you’d like.
Later users of your code have the option of associating a certain preset (wrapper class with saved settings in an XML file or DB) with whatever they’d like- so, for example, artists can specify their own configurations and associate them with certain textures (the ‘data object’ for the texture, that contains its logic in your pipeline, contains a reference to that configuration).
Untested-but-compiling sample code follows:
[highlight=c#]
public enum ResizingFilter
{
triangle,
box,
cone
}
public enum CompressionType
{
dxt1,
//dxt3, //We don’t want anyone using this
dxt5,
dxt5nm
}
public class CommandlineArgAttribute : System.Attribute
{
public string CommandlineName { get; private set; }
public CommandlineArgAttribute(string commandlinename)
{
CommandlineName = commandlinename;
}
}
public class SomeConverter
{
public const string ConverterExeFilename = @“Z:\art\utilities\some_converter.exe”;
public System.Drawing.Size TextureSize { get; set; }
[CommandlineArgAttribute("-f")]
public ResizingFilter ResizingFilter { get; set; }
[CommandlineArgAttribute("-q")]
public CompressionType CompressionType { get; set; }
private string m_folderPath;
public string FolderPath
{
get { return m_folderPath; }
set
{
m_folderPath = "\"" + value.TrimEnd('/', '\\', '*', '\"') + "\\*\"";
}
}
private string GetCommandlineArgsString()
{
List<string> args = new List<string>();
foreach (System.Reflection.PropertyInfo pi in this.GetType().GetProperties())
{
List<CommandlineArgAttribute> clattrs = pi.GetCustomAttributes(typeof(CommandlineArgAttribute), true).OfType<CommandlineArgAttribute>().ToList();
if (clattrs.Count > 0)
{
args.Add(clattrs[0].CommandlineName);
args.Add(pi.GetValue(this, null).ToString());
}
}
//You can figure out a more procedural way to do these complex-as-simple properties, or code width and height seperately, etc- this is the simplest and not a great option.
args.Add("-width");
args.Add(TextureSize.Width.ToString());
args.Add("-height");
args.Add(TextureSize.Height.ToString());
args.Add(FolderPath);
return String.Join(" ", args.ToArray());
}
public int RunConverter()
{
return RunConverter(true);
}
public int RunConverter(bool waitForExit)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = ConverterExeFilename;
proc.StartInfo.Arguments = GetCommandlineArgsString();
proc.Start();
if (waitForExit)
{
proc.WaitForExit();
}
return proc.ExitCode;
}
}