Click here to Skip to main content
Click here to Skip to main content

External tools add-in with ProcessStartInfo and Process classes

, 6 Dec 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    Share

    About the Author

    Ali Ozgur
    Team Leader PragmaTouch
    Turkey Turkey
    - Software developer
    - Has BS degree in Computer Engineering
    - Has MBA degree
    - Programmed with C, C++, Delphi, T-SQL and recently C#
    - Little educational experience with Prolog
    - Feel enthusiasm about NHibernate and LINQ
    - Love to develop on Cuyahoga Web Framework
    - Developer of PragmaSQL Editor
    (Code Project Members Choice Winner for 2009 and 2010)
    - Developed JiraTouch and MoodleTouch for iPhone
    - PragmaTouch Lead (www.pragmatouch.com)
    Follow on   Twitter

    Comments and Discussions

     
    -- There are no messages in this forum --
    | Advertise | Privacy | Terms of Use | Mobile
    Web01 | 2.8.141223.1 | Last Updated 7 Dec 2007
    Article Copyright 2007 by Ali Ozgur
    Everything else Copyright © CodeProject, 1999-2014
    Layout: fixed | fluid