使用 Mono 构建跨平台网络应用程序

作者:Ian Pointer

Mono 是 Ximian 对微软 .NET 开发框架的开源实现。.NET 包含几种不同的技术:一组用于多种不同语言(包括微软的新语言 C#)的编译器,这些编译器生成平台无关的字节码;一个称为公共语言运行时 (CLR) 的虚拟机,用于运行这些字节码;以及一个类库,其中包含大量有用的程序,用于执行从文件 I/O 到 GUI 创建和操作的各种操作。

Mono 实现包括一个可在 Linux、基于 BSD 的系统(包括 Mac OS X)和 Windows 上运行的 CLR,以及 C# 和 Basic 的编译器。Mono 仍在开发中,.NET 类库的许多部分尚未实现,特别是包含用于 Windows GUI 类的 Windows.Forms 组。但是,Mono 开发人员发布了 GTK 用户界面工具包的绑定,因此即使没有 100% 的 .NET 兼容性,也可以构建跨平台图形应用程序。本文介绍了如何使用 C#、Mono 和 Linux 编写一个有用的程序 MonoBlog,该程序可以在任何运行 Mono 和 GTK 的系统上运行。假定读者对 Glade 和 C# 有一定的了解,但只需基本水平即可。有用的教程可以在在线资源部分找到。

获取 Mono

Mono 网站提供了在 Linux、Mac OS X 和 Windows 平台上安装系统的说明(请参阅资源)。您还需要两个额外的 C# 库,GTK# 和 XmlRpcCS。MonoBlog 运行的系统需要基本的 GTK 库,这些库在大多数 Linux 系统上都可用。但是,它们可能需要在 Windows 和 Mac OS X 系统上安装;软件包可以在 GTK 网站上找到(请参阅资源)。有关安装这些库的说明可以在它们各自的网页上找到。

MonoBlog,一个 Weblog 编辑器

MonoBlog 是一个 Weblog 编辑器,可以向 Weblog 添加新文章和编辑旧文章,并为用户提供更改配置设置的方法。大多数 Weblog 系统都实现了一个名为 MetaWeblog API 的通用功能基础。MonoBlog 使用它与各种不同的 Weblog 程序进行通信,而不是为 Movable Type、LiveJournal 或 Radio Userland 系统编写单独的后端。此示例的完整 C# 代码可在 Linux Journal FTP 站点上找到(请参阅资源)。

图 1 和图 2 显示了 MonoBlog 的用户界面,该界面使用 Glade 在 Linux 上创建。图 1 中的主窗口具有用于输入 Weblog 标题和内容的文本控件,以及一系列用于更新 Weblog、清除表单和退出程序的按钮。左侧的白色区域是一个 GTKTreeView 控件,它显示一个旧文章列表,用户可以单击这些文章进行更新。图 2 中显示的窗口是一个简单的首选项面板,用户可以在其中输入允许 MonoBlog 与其 Weblog 通信的信息。

Cross-Platform Network Applications with Mono

图 1. 主窗口

Cross-Platform Network Applications with Mono

图 2. 首选项窗口

使用 libglade 创建 GUI

GTK 的一个有用功能是 libglade,这是一个库,允许我们通过读取 Glade 创建的 XML 文件来构建程序的 GUI,并在代码本身中指定 widget 的布局。GTK# 绑定包含此功能,因此构建 GUI 非常容易。在 MonoBlog 的开头,我们使用以下代码导入 GTK 和 Glade 命名空间using语句。然后,在构造函数中,我们有

Application.Init();

Glade.XML gxml = new Glade.XML("monoblog.glade",
                               null, null);
gxml.Autoconnect(this);

Application.Run();

所有 GTK 程序中都需要调用 Application 类。Application.Init() 执行 GTK 初始化,Application.Run() 将程序的控制权传递给 GTK 主循环,该循环监视事件并在事件发生时报告信号。标准的 Glade.XML 构造函数接受三个参数:一个包含 Glade 文件名的字符串,一个告诉对象 Glade 树中它应该开始构建界面的节点的字符串,以及最后,一个可用于指定 Glade 文件翻译域的字符串。

MonoBlog 需要访问 XML 文件中的所有节点,包括主窗口和首选项面板。不需要翻译,因此第二个和第三个参数为 null。Autoconnect() 方法将作为参数给出的对象与 Glade 文件中定义的信号处理程序和对象绑定,从而允许该对象响应事件和操作 widget。由于 MonoBlog 是一个小程序,我已将所有信号处理都包含在主类中。在更大、更复杂的系统中,最好将信号处理分离到另一个类中。

要访问 widget,需要进行特殊声明。widget 必须声明为实例变量,使用自定义属性

[Glade.Widget] GTKWidgetType widgetname;

withGTKWidgetType被替换为相关的实际对象类型,并且widgetname是 Glade 文件中定义的 widget 的名称。在 Autoconnect() 返回后,这些 widget 可以像在程序本身中创建一样使用。

检索旧条目

当程序加载时,它执行的第一个操作是查询 Weblog 并下载最近的文章,并将它们显示在 TreeView widget 中。MonoBlog 类中的方法 getRecentPosts() 处理此操作;如果存在首选项,则在主构造函数中调用它,因为该方法需要知道它正在联系的 Weblog。MetaWeblog API 提供了一个函数调用 metaweblog.getRecentPosts,它返回指定数量的旧文章,或者如果存在的文章少于我们想要的数量,则返回它能找到的所有文章。

与 Weblog 的通信很简单

XmlRpcRequest client = new XmlRpcRequest();
client.MethodName = "metaWeblog.getRecentPosts";
client.Params.Add(BlogID);
client.Params.Add(ServerUser);
client.Params.Add(ServerPass);
client.Params.Add(NumberOfPosts);
XmlRpcResponse response = client.Send(ServerURL);

所有需要做的就是创建一个新的 XmlRpcRequest 对象,设置所需的 API 方法名称,填写必要的参数并将其发送到 Weblog。然后,Weblog 返回一个响应,在本例中是一个文章数组,该数组存储在 XmlRpcResponse 对象的 Value 字段中。接下来,我们需要更新 GTKTreeView 控件。

在 GTK 2.0 及更高版本中,此控件使用模型-视图-控制器方法。因此,在这里,我们创建一个新的模型对象并将其传递给控件

System.Type[] ListTypes = new System.Type[3];
ListTypes[0] = typeof(string);
ListTypes[1] = typeof(string);
ListTypes[2] = typeof(string);
ListStore store = new ListStore(ListTypes);
treeview1.Model = store;

此模型对象创建一个包含三列的表。ListStore 对象需要传递一个 Type 对象数组;数组中的每个项目都对应于列的类型。Weblog 文章包含三个项目——标题、文章内容和一个唯一标识符——所有这些都是字符串,因此这里所有列都具有 String 类型。该方法的其余部分循环遍历数组,填充模型

TreeIter iter = new TreeIter ();
foreach (Hashtable post in results) {
  String title = (String) post ["title"];
  String postid = (String) post ["postid"];
  String description = (String) post ["description"];

  store.Append (out iter);
  store.SetValue (iter, 0, new GLib.Value(title));
  store.SetValue (iter, 1, new GLib.Value(postid));
  store.SetValue (iter, 2,
  new GLib.Value(description));
}

仅凭这一点,不足以在树中显示标题。为此,我们在构造函数中包含一些代码,在调用 getRecentPosts() 之后

TreeViewColumn TitleCol = new TreeViewColumn();
CellRenderer TitleRenderer = new CellRendererText();
TitleCol.AddAttribute (TitleRenderer, "text", 0);
treeview1.AppendColumn (TitleCol);

这向树中添加了一个新的列视图。AddAtrribute() 方法使用参数 0 挂钩到模型的标题列(第一个)。由于用户只需要在 TreeView 控件中看到条目的标题;因此不需要其他列视图。但是,信息存储在模型中以提高程序的效率。

编辑旧文章

当用户单击一个条目时,期望的结果是程序在窗口右侧的文本输入部分中显示旧条目。MetaWeblog API 有一个名为 metaweblog.getPost 的方法,用于从 Weblog 中拉取文章。由于它们已经在 getRecentPosts() 方法中下载,因此程序可以从模型中获取数据,而无需再次与 Weblog 通信。row_activated 信号使用 Glade 绑定到方法 SelectOldPost,因此每当双击一个项目时,此代码就会运行

public void SelectOldPost(System.Object obj, EventArgs e) {
  TreeSelection currentSelection = treeview1.Selection;
  TreeIter iter;
  TreeModel model = treeview1.Model;
  currentSelection.GetSelected (out model, out iter);
  String selected = (string) model.GetValue (iter, 1);
  String oldTitle = (string) model.GetValue(iter,0);
  String oldEntry = (string) model.GetValue(iter,2);

  TextBuffer buffer = textview1.Buffer;
  entry1.Text = oldTitle;
  buffer.SetText(oldEntry);

  OldPostID = selected;
  EditingOldPost = true;
}

此方法获取 GTKTreeView 控件中当前选定的项目,并使用迭代器索引到模型中并找到所需的值。然后,它用此信息填充文本字段,并更新用户单击“发布”按钮时需要的两个实例变量。如果程序正在更新旧条目而不是创建新条目,则需要进行不同的 MetaWeblog API 调用,这需要文章的唯一标识符。变量 OldPostID 和 EditingOldPost 会更新以反映这一点。

更新 Weblog

“更新”按钮的 clicked 信号绑定到方法 OnUpdateClicked。此过程太长,无法在本文中完整重印,但操作非常简单。首先,它从两个文本控件获取文本,并创建文章的哈希表表示形式;这是 MetaWeblog API 调用所必需的。根据是否设置了 EditingOldPost 标志,该方法然后使用以下命令向 Weblog 发送 XML-RPC 请求metaweblog.newPostmetaweblog.editPost调用,视情况而定。当 Weblog 返回成功的响应,表明它已更新时,该方法最终清除文本表单,并允许用户重新开始。

主窗口上的其他按钮“新建文章”和“退出”是简短的代码片段。与“发布”按钮一样,clicked 信号在 MonoBlog 中绑定。“新建文章”绑定到一个清除文本表单并将 EditingOldPost 标志设置为 false 的方法,允许用户重新开始。正如预期的那样,“退出”使用 Application.Exit() GTK 调用退出 MonoBlog。

首选项

.NET 类库包含处理从 XML 文件读取首选项的类。清单 1 显示了一个示例 MonoBlog 配置文件。

清单 1. 示例 XML 配置文件

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="ServerURL"
value="http://www.test.com/mt-xmlrpc.cgi"/>
<add key="ServerUser" value="example"/>
<add key="ServerPass" value="password"/>
<add key="BlogID" value="1"/>
<add key="NumberOfPosts" value="10"/>
</appSettings>
</configuration>

下面显示的 getConfig() 方法读取这些值

private bool getConfig {
  try {
  AppSettingsReader config = new AppSettingsReader();
  ServerURL = (string) config.GetValue("ServerURL",
  typeof(string));

  ServerUser = (string)  config.GetValue("ServerUser",
  typeof(string));

  ServerPass = (string) config.GetValue("ServerPass",
  typeof(string));

  BlogID = (string) config.GetValue("BlogID",
  typeof(string));

  NumberOfPosts =  (string)
  config.GetValue("NumberOfPosts", typeof(string));

  catch(Exception problem) {
    return false;
    }
  return true;
}

默认情况下,AppSettingsReader 对象查找名为 executable.config 的配置文件,因此此处它打开一个名为 monoblog.exe.config 的文件。然后,GetValue() 方法用于确定所需的首选项值。MonoBlog 在尝试查询 Weblog 以获取旧文章之前,在其构造函数中调用此方法,因此它具有所需的信息。如果文件不存在或读取数据时出现问题,则该方法返回 false。构造函数仅在该方法返回 true 时调用 getRecentPosts(),以防止使用垃圾值。

更新首选项是一项更复杂的任务。首先,主窗口菜单栏中的“首选项”选项使用 Glade 的菜单编辑器绑定到方法 OnPrefsActivate。这将弹出图 2 中显示的对话框,并使用当前值(如果已定义)填充字段。当用户在此对话框中单击“确定”按钮时,MonoBlog 会更新变量并将新信息写回配置文件。不幸的是,.NET 类库没有更新配置文件的类。由于此处的配置非常简单,因此我编写了一个名为 saveConfig() 的方法,该方法打开默认配置文件并使用一系列 Write() 语句将更新后的信息写回磁盘。这可以用更复杂的方法来代替,该方法构建正确的 XML 文档,但对于此应用程序来说,以简单的方式写出值更容易。

错误处理

由于 MonoBlog 会调用 Internet,其中可能会发生程序无法控制的错误(网络错误、名称服务器问题等),因此它包含一些基本的错误处理功能。getRecentPosts() 和 OnUpdateClicked() 方法包装在 try...catch 块中。执行访问 Internet 的代码,如果出现问题,则运行以下 catch 块

catch(Exception problem) {
  MessageDialog md =
  new MessageDialog(MonoBlogWindow,
  DialogFlags.DestroyWithParent,
  MessageType.Error,
  ButtonsType.Close,
  problem.ToString());

  md.Run();
  md.Destroy();
}

这会导致屏幕上出现一个错误对话框,其中包含 Mono CLR 报告的问题作为文本消息。这允许用户继续并可能修复问题。但是,在撰写本文时,异常支持在 Mono CLR 的 PPC 分支中不起作用,因此如果程序在 Mac OS X 上运行,则异常机制不起作用,程序会静默失败。但是,PPC 端口的工作正在进行中,因此到本文印刷时,这种缺乏支持可能不再是一个问题。

编译和运行

编译 C# 程序是通过 mcs 编译器完成的。MonoBlog 使用以下命令编译

mcs -r gtk-sharp.dll -r glade-sharp.dll \
-r XmlRpcCS.dll -r glib-sharp.dll monoblog.cs

-r 选项指示程序需要的资源;这里,我们只需要指定 MonoBlog 使用哪些库。这将生成一个名为 monoblog.exe 的编译字节码文件。要运行该程序,我们需要使用此文件作为参数运行 Mono CLR

mono monoblog.exe

现在,在 Linux 上开发程序后,我们可以以最少的麻烦在 Windows 或 Mac OS X 上运行该程序。只需将 monoblog.exe、monoblog.exe.config 和 monoblog.glade 文件复制到另一个平台,并使用 Mono CLR 运行它们,如上所示。

图 3、图 4 和图 5 分别显示了 MonoBlog 在 Linux、Windows 和 Mac OS X 机器上运行的情况。无需更改任何代码;只要 MonoBlog 使用的所有库都存在,程序就可以按原样工作。

Cross-Platform Network Applications with Mono

图 3. MonoBlog 在 Red Hat 9 上

Cross-Platform Network Applications with Mono

图 4. MonoBlog 在 Windows XP 上

Cross-Platform Network Applications with Mono

图 5. MonoBlog 在 Mac OS X 上

结论

希望本文已经演示了如何快速轻松地使用 Mono 和 C# 创建跨平台应用程序。您可以在一个平台上开发,并确信该程序可以在任何运行 Mono 和 GTK 库的平台上运行。MonoBlog 程序本身非常适合进一步实验。一些可能的改进领域包括额外的格式化选项、更详细的错误报告、使用 GtkHTML# 绑定创建 HTML 预览窗口,以及 MetaWeblog API 的进一步实现,例如添加从 Weblog 中删除文章的功能。

本文资源: /article/7557

Ian Pointer 是英国一位失业的计算机科学专业毕业生。您可以通过 ian@snappishproductions.com 与他联系。

加载 Disqus 评论