使用 ProcessStartInfo 和 Process 类进行外部工具插件开发
介绍如何使用 System.Diagnostics 中的 ProcessStartInfo 和 Process 类制作外部工具插件
下载源代码
下载 PragmaSQL 外部工具插件源代码引言
在本文中,我们将展示如何利用 System.Diagnostics 命名空间中的 ProcessStartInfo 和 Process 类为 PragmaSQL 编辑器开发一个非常常见的实用工具。
PragmaSQL T-SQL 编辑器具有非常广泛的插件支持。本文介绍的外部工具插件有两个目标:
1- 作为 PragmaSQL 插件开发的示例。
2- 提供所有开发 IDE 和编辑器中包含的非常常见的功能。
IC#Code 插件架构概述
PragmaSQL 使用 IC#Code 的插件架构。该架构提供了
出色的功能,使我们能够轻松地为应用程序开发插件,从而
使我们的应用程序具有可扩展性。IC#Code 插件架构提供了
1- 通过 .addin 文件进行简单而整洁的 XML 插件定义
2- 加载插件程序集
3- 通过宿主应用程序的菜单和工具栏公开插件功能
4- 以及许多实用服务,如 MenuService 和 MessageService
有关架构的详细信息,请参阅 SODA - SharpDevelop Open Development Architecture by Mike Krueger
PragmaSQL 服务概述
PragmaSQL 通过核心库向插件开发者公开了许多内置的宿主功能。
公开的功能包括
1- 宿主选项
2- 编辑器服务:访问 T-SQL 脚本编辑器和文本编辑器
3- 对象浏览器服务:访问数据库对象浏览器
4- 项目浏览器服务
5- 共享脚本和代码片段
6- 内部网络浏览器
7- 文本差异服务
8- 代码完成列表
9- 应用程序消息服务
所有这些服务的入口点是 HostServicesSingleton,顾名思义,这个类是一个单例。
PragmaSQL.ExternalTools 中的 HostServicesSingleton 用法示例
下面提供的示例代码展示了
1) 如何使用应用程序消息服务将消息打印到宿主应用程序消息窗口。
2) 使用 HostServices EvalMacro 函数评估宏并准备工具参数
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;
}
注意:为了开发 PragmaSQL 插件,您需要获取 PragmaSQL.Core.dll。所有宿主功能
和许多实用程序类都托管在此程序集中。从此处下载
PragmaSQL.ExternalTools
插件定义
<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>
在上面的插件定义文件中,我们提供了插件的描述以及插件如何集成到 PragmaSQL。
此插件定义中最重要的部分是 Path 标签。我们提供了预定义的宿主路径以及我们希望为插件创建的 MenuItems
我们希望为我们的插件创建的菜单项。
另一个非常重要的标签是 Class。IC#Code 插件架构使用命令模式。
我们通过此标签使用 Command 类定义与指定菜单/工具栏项目关联的命令。
在上面的示例中,对于 External Tools... 菜单项,您可以看到我们希望调用 PragmaSQL.ExternalTools.ConfigureTools 命令。
ConfigureTools 命令类继承自 AbstractMenuCommand 并具有 Run() 方法。
注意:预定义的宿主路径可以在 PragmaSQL 安装附带的 Base.addin 文件中找到。
ExternalTools 插件特定类
静态实例。工具配置项可通过 Current 公共静态属性访问。
此方法返回以下 DialogResult 枚举值之一
OK:用户按下 OK 按钮并应用了工具配置的更改
Ignore:用户按下 OK 按钮且工具配置没有更改
Cancel:用户按下 Cancel 按钮。
使用 Process 和 ProcessStartInfo 运行工具
.NET Framework 在 System.Diagnostics 命名空间下提供了 ProcessStartInfo 和 Process 类,可用于从我们的代码中运行外部进程。
ExternalTools 插件从 RunToolForm 中使用这些类。
RunTool 函数如下所示
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);
}
如您所见,启动进程没有什么特别之处。我们只需使用 ProcessStartInfo 类定义进程的文件名、参数和工作目录,
然后使用此 ProcessStartInfo 创建一个 Process 实例。
此实现中值得注意的几点是
1) 标准输出和错误重定向。
您可以将进程的标准输出/错误重定向到任何您喜欢的位置,前提是将
- ProcessStartInfo 实例的 RedirectStandardOutput 和 RedirectStandardError 属性设置为 true
- ProcessStartInfo 实例的 UseShellExecute 属性设置为 false
- 外部工具将输出和错误打印到标准输出。例如,您无法重定向 Windows 应用程序的输出,
但您可以重定向控制台应用程序的输出。
2) 同步与异步输出读取
Process 类提供重定向输出/错误的同步与异步读取。然而在实现中,
选择其中一种方法会产生很大的不同。在我们的实现中,看起来我们选择了同步
输出读取。但这并非如此。再次查看 RunTool 方法。我们通过第 17 行的 p.Exited += new EventHandler(p_Exited); 附加到
Process 实例的 Exited 事件。在考虑同步读取后,
p_Exited 方法抛出了跨线程调用异常。此异常表明了两个问题:
- 1- p_Exited 方法是从不同的线程调用的,因此表明操作是异步的
- 2- PragmaSQL 消息服务不支持跨线程调用。
我只需通过添加一些 Invoke() 相关代码来修复 PragmaSQL 消息服务以支持跨线程调用,就是这样。
结论
外部工具支持是 PragmaSQL 的一项通用要求。在本文中,我们介绍了 IC#Code 插件支持、PragmaSQL 如何利用
IC#Code 插件架构、PragmaSQL 公开了哪些服务来提供可插拔/可扩展的应用程序以及一些关于
PragmaSQL 插件开发的初步见解,并附有源代码示例。