Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

External tools add-in with ProcessStartInfo and Process classes

0.00/5 (No votes)
6 Dec 2007 1  
Describes making of an external tools add-in using ProcessStartInfo and Process classes in System.Diagnostics

Download Source Code

Download PragmaSQL External Tools Add-In Source

Introduction

In this article we show how to develop a very common utility with ProcessStartInfo and Process classes included in System.Diagnostics namespace for PragmaSQL editor.

PragmaSQL T-SQL editor has very extensive add-in support. External Tools add-in presented in this article has two goals

    1- Serve as PragmaSQL add-in development example.
    2- Provide a very common feature included in all development IDEs and editors.

IC#Code Add-In Architecture Overview

PragmaSQL makes use of IC#Code's Add-In architecture. This architecture provides
fantastic fatures that enables us to develop plugins for our applications easily thus
making our applications extendable. IC#Code Add-In architecture provides

    1- Simple and neat XML add-in definition via .addin files
    2- Loading of add-in assemblies
    3- Expose add-in functionality through menus and toolbars of the host application
    4- And many utility services like MenuService and MessageService

For details about the architecture please refer to SODA - SharpDevelop Open Development Architecture by Mike Krueger

PragmaSQL Services Overview

PragmaSQL exposes many of the built-in host features to the add-in developers with a core librarary.
Exposed features include

    1- Host Options
    2- Editor Services: Access to T-SQL script editor and text editor
    3- Object Explorer Service: Access to database object explorer
    4- Project Explorer Service
    5- Shared Scripts and Code Snippets
    6- Internal web browser
    7- Text Diff Service
    8- Code completion lists
    9- Application messages service

Entry point to all these services is HostServicesSingleton, as its name mimics this class is a singleton.

HostServicesSingleton Usage Example From PragmaSQL.ExternalTools

Example code provided below shows

1) How we can use Application Message Service to print messages into Host Application Messages window.
2) Evaluate macros with HostServices EvalMacro function and prepare tool arguments

private void process_Exited(object sender, EventArgs e)
{
    Process p = sender as Process;
    if (p == null)
        return;

    long handle = p.Handle.ToInt64();
    if (!_runningToolDefs.ContainsKey(handle))
        return;

    try
    {
        ExternalToolDef def = _runningToolDefs[handle];
        
        //Here we clear application messages window



        if (chkClearOutput.Checked)
            HostServicesSingleton.HostServices.MsgService.ClearMessages();

        bool shallShow = false;

        while (p.StandardOutput.Peek() > -1)
        {
            string info = p.StandardOutput.ReadLine();
            if (!String.IsNullOrEmpty(info))
            {
                // Print info message to Applicatin Messages Window



                HostServicesSingleton.HostServices.MsgService.InfoMsg(info, "Tool : " + def.Title, String.Empty, String.Empty);
                shallShow = true;
            }
        }

        while (p.StandardError.Peek() > -1)
        {
            string error = p.StandardError.ReadLine();
            if (!String.IsNullOrEmpty(error))
            {
                // Print error message to Applicatin Messages Window



                HostServicesSingleton.HostServices.MsgService.ErrorMsg(error, "Tool : " + def.Title, String.Empty, String.Empty);
                shallShow = true;
            }
        }

        if (shallShow == true)
            HostServicesSingleton.HostServices.MsgService.ShowMessages();
    }
    finally
    {
        _runningToolDefs.Remove(handle);
    }
}




private void RenderExternalToolDef(ExternalToolDef exDef)
{
    tbCmd.Text = String.Empty;
    tbArgs.Text = String.Empty;
    tbWorkingDir.Text = String.Empty;
    
    if (exDef == null)
        return;
        
    tbCmd.Text = exDef.Command;
    // Here we prepare arguments by evaluating macros



    tbArgs.Text = HostServicesSingleton.HostServices.EvalMacro(exDef.Args);
    tbWorkingDir.Text = exDef.WorkingDir; 
}

        

NOTE: In order to develop PragmaSQL Add-Ins you need to get PragmaSQL.Core.dll. All host functionality
and many utility classes are hosted in this assembly.Download from here

PragmaSQL.ExternalTools

Add-In Definition

<AddIn name        = "External Tools AddIn for PragmaSQL"
       author      = "Ali �zg�r"
       description = "Enables you to define external tools for PragmaSQL">
    <Manifest>
        <Identity name = "PragmaSQL.ExternalTools"/>
    </Manifest>

    <Runtime>
        <Import assembly="PragmaSQL.ExternalTools.dll"/>
    </Runtime>

    <Path name = "/Workspace/ToolsMenu">
        <MenuItem id = "ExtTools.Configure"
                         label = "External Tools..."
                         class ="PragmaSQL.ExternalTools.ConfigureTools"/>
        <MenuItem id = "ExtTools.Run"
                         label = "Run External Tool"
                         shortcut     = "Control|Shift|E"
                         class ="PragmaSQL.ExternalTools.RunExternalTool"/>
    </Path>
</AddIn>

In the add-in definition file above we provide the description of our add-in and how our add-in integrates to PragmaSQL.
Most important part of this add-in definition is the Path tag. We provide the predefined host path along with the MenuItems
we want to be created for our add-in.

Another very important tag is Class. IC#Code Add-In architecture makes use of Command Pattern.
We define commands associated to the specified menu/toolbar items with Command classes through this tag.
In the above example for External Tools... menu item you can see that we want PragmaSQL.ExternalTools.ConfigureTools command
to be invoked. ConfigureTools command class inherits from AbstractMenuCommand and has Run() method.

NOTE: Predefined host paths can be found in Base.addin file that comes with PragmaSQL installation.

ExternalTools Add-In Specific Classes

  • ExternalToolDef: Serialazable class which is used to hold external tool configuration data for a single tool
  • ExternalToolsCfg: Static class which is used to load and save serialized tool configuration data from a file into an IList<ExternalToolDef>
    static instance.Tool configuration items are accessible through Current public static property.
  • ConfigureTools: Command class inherited from AbstractMenuCommand. This command is used to show tool configuration form
  • RunExternalTool: Command class inherited from AbstractMenuCommand. This command is used to show run tool form.
  • ConfigForm: Tool configuration form. Form is opened with ConfigureExternalTools public static method.
    This method returns one of these DialogResult enumeration values

      OK: User pressed OK button and changes to tool configuration applied

      Ignore: User pressed OK button and no changes to tool configuration exist

      Cancel: User pressed Cancel button.

  • RunToolForm: The form that lists external tool definitions and used to run a selected tool.

    Running a tool by using Process and ProcessStartInfo

    .NET Framework provides ProcessStartInfo and Process classes under System.Diagnostics namespace that can be used to run an external process.
    from our code. ExternalTools add-in makes use of these classes from RunToolForm.

    RunTool function looks like:

    private void RunTool()
    {
        if (CurrentDef == null)
            return;
    
        ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
        tbArgs.Text = HostServicesSingleton.HostServices.EvalMacro(CurrentDef.Args);
        psi.FileName = CurrentDef.Command;
        psi.Arguments = tbArgs.Text;
        psi.WorkingDirectory = CurrentDef.WorkingDir;
        psi.RedirectStandardOutput = CurrentDef.UseOuput;
        psi.RedirectStandardError = CurrentDef.UseOuput;
        psi.CreateNoWindow = CurrentDef.UseOuput;
        psi.UseShellExecute = !CurrentDef.UseOuput;
        
        Process p = new Process();
        p.EnableRaisingEvents = CurrentDef.UseOuput;
        if (CurrentDef.UseOuput)
            p.Exited += new EventHandler(p_Exited);
        
        p.StartInfo = psi;
        p.Start();
    
        if (CurrentDef.UseOuput)
            _runningToolDefs.Add(p.Handle.ToInt64(), CurrentDef);
    }
    

    As you can see there is nothing special about starting a process. We simply define the fileName, arguments and working directory of the

    process with a ProcessStartInfo class and then create a Process instance using this ProcessStartInfo.
    Interesting points in this implementation are

    1) Standard output and error redirecting.

    You can redirect standard output/error from a process anywhere you like providing these

    • RedirectStandardOutput and RedirectStandardError properties of the ProcessStartInfo instance set to true
    • UseShellExecute property of ProcessStartInfo instance set to false
    • External tool prints output and errors to standard output. For example you can not redirect output from a windows application
      however you can redirect output from a console application.

    2) Synchronous vs Asynchronous Output Reading

    Process class provides synchronous vs asynchronous reading of redirected output/error. However in implementation
    choosing between one of the methods makes a big difference.In our implementation it seems as if we have chosen synchronous
    output reading. But that is not true. Take a look at the RunTool method again. We attach to the Exited event of the
    Process instance with p.Exited += new EventHandler(p_Exited); on line 17. After implementing synchronous read in mind
    p_Exited method threw cross-thread call exception. This exception indicated two problems

    • 1- p_Exited method was called from a different thread thus indicating the operation was asynchronous
    • 2- PragmaSQL Message Service did not supported cross thread calls.

    Simply I fixed PragmaSQL Message Service to support cross thread calls by adding some Invoke() related code and that was it.

    Conclusion

    External tool support was a general requirement for PragmaSQL. In this article we covered IC#Code Add-In support, how PragmaSQL makes use of
    IC#Code Add-In architecture, what services does PragmaSQL exposes to provide a pluggable/extendable application and some initial insight
    to PragmaSQL Add-In development with source code examples.

  • License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here