IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux  >

创建本机、跨平台 GUI 应用程序

GCJ、Linux 和 SWT 如何集合在一起来解决 Java UI 难题

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Kirk Vogen (kirkvdevw@us.ibm.com), I/T 专家, IBM Global Services

2002 年 4 月 01 日

Java 语言在服务器端和 applet 领域已经大行其道,但是对于最终用户应用程序,通常它不是主角。其实不一定会是这种情况。使用 Linux、用于 Java 的 GNU 编译器(GNU Compiler for Java)和标准窗口构件工具箱(Standard Widget Toolkit),您可以创建用 Java 语言编写的快速、本机 GUI 应用程序。(Windows 版本的也不远了。)经验丰富的 Java 开发人员 Kirk Vogen 说明了如何做到这一步。通过单击本文顶部或底部的 讨论论坛中将您对本文的想法与作者和其他读者一起分享。

众所周知,Java 语言在服务器上以及 applet 领域已经非常成功了,但是它在最终用户应用程序领域为什么没有大行其道呢?有几个原因。首先,即使很小的应用程序的内存占用通常也有好几兆字节。第二,与 Java 语言一起提供的 GUI 库产生的应用程序通常看起来与其本机同类应用程序不同。因此,无论您的应用程序多么健壮或稳定,与本机应用程序相比,它都显得非常笨拙。

用于 Java 的 GNU 编译器

让我们从内存占用问题开始。Java 应用程序要使用额外的内存,因为运行 Java 字节码时,虚拟机必须完成许多“工作”。在当今高级编译器中,编译即时(just-in-time)发生并且编译器必须对这一信息立即(on-the-fly)进行高速缓存以供以后使用。当然,现在内存是便宜,但是当有几个 Java 应用程序同时在一台机器上运行时,即使是大机器也可能由于持续的内存页面调度而放慢速度。进入用于 Java 的 GNU 编译器(GCJ)。GCJ 获得 Java 源代码或字节码,然后将它们编译成本机机器代码。然后,可以将来自几个 Java 类的机器代码链接在一起成为单个本机应用程序。

一月份,Martyn Honeyford 为我们提供了本机编译和 GCJ 的简介(请参阅 参考资料以获取到 Martyn 文章的链接)。正如他通过示例向我们展示的那样,本机编译的应用程序使用的内存通常要比在虚拟机中运行的应用程序要少得多。





回页首


标准窗口构件工具箱

这很棒,但是正如 Martyn 指出的,GCJ 尚不支持 AWT 或 Swing。因此,我们现在将如何建立本机编译的 GUI 应用程序呢?进入标准窗口构件工具箱(SWT)。这一 API 是开放源码 Eclipse 工具平台的一部分。为了避免引起 Swing 与 SWT 的对抗(请参阅 参考资料以获取关于这场争论的更多详细信息),让我说明一些 SWT 的优势。

SWT 试图弥补 AWT 和 Swing 的缺点。使用 AWT,我们将受到“最小公分母”限制:仅支持存在于所有平台上的窗口构件。因为 Motif 没有提供本机树型窗口构件而 Windows 提供了该功能,AWT 就没有包含树型窗口构件。

Windows 上的 GCJ

Windows 上的 GCJ 还未实现。由于 GCJ 中存在一个涉及与场合无关的文件系统(例如,FAT 和 NTFS)的错误,GCJ 目前还不能在 Windows 上运行。但是,在 GCJ 3.1 中接下来最好的一件事应该出现。使用 Linux,您将能把 GCJ 设置成交叉编译器,它将允许您在 Linux 上编译代码而生成 Windows 二进制文件。GCJ 3.1 是针对 2002 年 4 月 15 日的发行版。请参阅 GCJ 主页和邮件列表(在 参考资料中提及的)以获取最新的发展。

Swing 走向了另一个极端。虽然带有一个很出色的 API 进行优雅地设计,Swing 还是自己实现窗口构件。因此 Swing 不依赖于操作系统提供窗口构件。无论本机是否支持,这都为 Swing 提供了不可思议的灵活性。但是,因为 Swing 自己绘制这些窗口构件,所以最终的外观看起来与本机应用程序有明显的不同。

SWT 试图弥合这两个 GUI 工具箱之间的差距。它的行军命令是:“如果有本机窗口构件就使用它。如果没有,就模拟它。”前面提到的树型窗口构件就是这样一个示例。因为 Windows 支持本机树型窗口构件,所以在 Windows 上运行时,SWT 就使用它。但是,Motif 不支持树型窗口构件,因此 SWT 在 Motif 下运行时绘制其自己的窗口构件版本。使用 SWT,结果应用程序看起来与其本机的同类应用程序很相似,因为尽可能地使用了本机窗口构件。





回页首


设置开发环境

因此,如何在本机编译所有这些应用程序呢?首先,您需要一个使用 GCJ 设置的开发环境。目前,在 Linux 和其它 UNIX 变体(请参阅“Windows 上的 GCJ”侧栏以了解 GCJ 在 Windows 上的情况)上支持 GCJ。设置开发环境的最简便方法是安装一种 Linux 分发版的新近版本。Mandrake 8.1 和 Red Hat 7.2 都带有需另外安装的 GCJ 3.0.1。因为 GCJ 仍然在发展,所以可能想考虑使用最新和最好的版本。我选择在版本 3.0.3 和 3.0.4 上进行测试。手工安装 GCJ 相对容易;GCJ 页面提供了相当好的文档(请参阅 参考资料)。

一旦有了一个具有 GCJ 的环境,则下载 Eclipse SDK 并安装它。这将为您提供 SWT 源代码和字节码。要安装 Eclipse SDK,请下载 ZIP 文件(在 Eclipse SDK 标题下),然后将它解压缩到一个目录中。我建议使用目录 /usr/local/eclipse,但是任何目录都可以。

开始编译

设置开发环境后,就可以开始编译了。编译应用程序包括对大多数 SWT 源代码执行与下面类似的命令:

gcj -c MyClass.java -o MyClass.o

大约有 30 个 SWT 源文件使用 Java 本机接口(Java Native Interface (JNI)),因此我们需要以略微不同的方式来编译它们(请注意 -fjni 标记):

gcj -fjni -c MyJNIClass.java -o MyJNIClass.o

最后,我们需要将结果对象编译成共享对象:

gcj -shared -o swt.so MyClass.o MyJNIClass.o ...





回页首


构建 SWT

在本文中,我们将把 SWT 编译成共享对象,然后在产生的应用程序中动态引用这个对象。请注意,您可以使用应用程序代码将 SWT 编译成可执行文件,但是基于本文所讨论的范围,我们将坚持编译成共享对象。正如它的名称所表明的,共享对象的主要优点就是可以被共享。在运行时所有应用程序都可以动态地使用同一个对象。产生的可执行文件大小将小得多。

使用下载源代码中包含的 Ant 构建文件(buildfile),我们将以自动方式编译 SWT。这个文件完成两个主要任务。首先,它将几个补丁程序应用到 SWT 源代码。(SWT 文件中有三个将不能用 GCJ 进行编译,这是因为一些小的编译器错误造成的 ― 例如,它不喜欢 int x, y, x1, y1 作为一行;必须将它们分成多行。在这些情况下,为这三个源文件编写了小的补丁程序以使它们在功能上等价。正如我以前提到的,GCJ 仍然在发展;这些错误会逐渐消除。)其次,构建文件使用提供的制作文件(makefile)来调用 make

在清单 1 中显示的 Ant 构建文件已经在 Ant 1.3 和 Ant 1.4.1 上测试过了。请注意,在清单中我除去了注释以节省空间,但是有完整的代码可供下载。

要运行构建,请遵循下列步骤:

  1. 安装 Ant(请参阅 参考资料以获取关于 Ant 的更多信息)。
  2. 下载本文的 源文件,它包含构建文件、补丁程序和关联的制作文件,然后将它解压缩到一个目录中。
  3. 导航至 SWT 目录,然后输入 ant 。构建文件假设您已经在 usr/local/eclipse 中安装了 Eclipse。如果 Eclipse 安装在另一个目录中,请输入 ant -Declipse_install_dir=your_directory ,其中 your_directory是您安装 Eclipse 的目录。

注:如果不想使用 Ant 构建文件,您可以手工解压缩 SWT 源文件和字节码,然后使用 UNIX patch 命令来应用源代码中包含的那三个补丁程序(使用 .patch 文件)。然后,运行制作文件。


清单 1. Ant 构建文件
<project name="BuildEclipse" default="cleanup">
  <!--
  The following properties can be overridden at the command-line.
    e.g. ant -Declipse_install_dir=/usr/local/eclipse
  -->
  <property name="eclipse_install_dir" value="/usr/local/eclipse"/>
  <property name="temp_dir" value="build_temp"/>
  <property name="shared_object_name" value="swt.so"/>
  <target name="init">
    <mkdir dir="${temp_dir}"/>
  </target>
  <target name="unpack" depends="init">
    <unzip src="${eclipse_install_dir}/plugins/org.eclipse.swt/swtsrc.zip"
           dest="${temp_dir}"/>
    <unjar src="${eclipse_install_dir}/plugins/org.eclipse.swt/swt.jar"
           dest="${temp_dir}"/>
  </target>
  <target name="patch" depends="unpack">
    <patch patchfile="TabFolder.patch"
           originalfile="${temp_dir}/org/eclipse/swt/widgets/TabFolder.java"/>
    <patch patchfile="Widget.patch"
           originalfile="${temp_dir}/org/eclipse/swt/widgets/Widget.java"/>
    <patch patchfile="TreeEditor.patch"/>
    <move file="TreeEditor.java" todir="${temp_dir}/org/eclipse/swt/custom"/>
  </target>
  <target name="make" depends="patch">
    <execon executable="touch">
      <fileset dir="${temp_dir}" includes="**/*.java" excludes="**/*.class"/>
    </execon>
    <execon executable="touch">
      <fileset dir="${temp_dir}" includes="**/*.class" excludes="**/*.java"/>
    </execon>
    <copy file="Makefile" todir="${temp_dir}"/>
    <exec executable="make" dir="${temp_dir}">
      <arg line="-f Makefile"/>
    </exec>
  </target>
  <target name="cleanup" depends="make">
    <move file="${temp_dir}/${shared_object_name}" todir="."/>
    <delete dir="${temp_dir}"/>
  </target>
</project>





回页首


创建应用程序

既然您已经将 SWT 构建到共享对象中,您就可以尝试样本应用程序。显示在清单 2 中的应用程序由一个显示经典“Hello, World!”消息的简单窗口组成。


清单 2.“Hello, World”样本应用程序
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.RowLayout;
public class Hello {
    public static void main(String[] args) {
        Display display = new Display();
        final Shell shell = new Shell(display);
        RowLayout layout = new RowLayout();
        layout.justify = true;
        layout.pack = true;
        shell.setLayout(layout);
        shell.setText("Hello, World!");
        Label label = new Label(shell, SWT.CENTER);
        label.setText("Hello, World!");
        shell.pack();
        shell.open ();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep ();
        }
       display.dispose ()

要编译这个应用程序,请遵循下列步骤:

  1. 将 SWT 共享对象(swt.so)复制到 Hello World 目录中,然后输入下列命令。(请记住,如果在 /usr/local/eclipse 以外的目录中安装了 Eclipse SDK,请用您的目录来替换它。)
    gcj -CLASSPATH=/usr/local/eclipse/plugins/org.eclipse.swt/swt.jar 
      -c Hello.java -o Hello.o
    gcj -main=Hello -o Hello Hello.o swt.so
    


  2. 接下来,使用下列命令设置库路径(假设您正在使用 bash shell):
    export LD_LIBRARY_PATH=
      .:/usr/local/eclipse:/usr/local/eclipse/plugins/org.eclipse.swt/ws/motif
    


  3. 输入 ./Hello ,会看到本机编译的 GUI Java 应用程序出现屏幕上!

注:本文的 源代码包括使这一过程自动化的制作文件。





回页首


展望

对 Windows 应用程序的编译很快就可以实现了(请参阅 Windows 上的 GCJ侧栏),虽然目前仅在 Linux/Motif 和 Windows 上支持 SWT,但正在进行积极的开发以在 Linux 上将 SWT 移植到 GTK/GNOME。SWT 还可以在 AIX 和 Solaris 上的 Motif 下工作,但是还未经全面测试。最后,在 Windows CE、QNX 和 Macintosh 移植上已经完成了一些工作,但是目前主要的侧重点还是针对其它平台。要查看各种 SWT 移植的进展,请参阅 参考资料





回页首


结束语

众所周知,Java 语言具有一些重要的特性。它是优雅的,提供了面向对象概念的出色实现,并且具有一个非常有用的标准类库。将这些优点与 GCJ 和 SWT 相结合使 Java 语言定位成在服务器和 Web 浏览器上建立其基础,然后转向最终用户应用程序领域。



参考资料



关于作者

Kirk Vogen 的照片

Kirk Vogen 是一名富有编程激情的 Java 老手,尤其是在各种面向对象的变化方面。他于 1995 年毕业于 St. Olaf College,然后在 IBM Global Services 致力于使用 WebSphere 构建电子商务应用程序。可以通过 kirkvdevw@us.ibm.com与他联系。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款