对于Eclipse开发者来说,不管Plug-in还是RCP免不了要和SWT打交道,但两者似乎有些不同,Plug-in主要跟Eclipse过招,开发更多是上层应用如UI/JDT/EMF/GMF等,而RCP像是应用程序,时不时与图形系统交互,需求更是五花八门,举几例:
    - RCP界面要跟Office 2007/Vista一样。
    
- 我喜欢上一VC版的水晶按钮。
    
- 商业版控件支持。
    
- 我的RCP程序要定时启动。 
这些效果让RCP下的SWT越来越跟平台相关,但矛盾的是SWT要跨平台,提供的API只能是一个平衡产品+少许补充,更多特性依赖我们对SWT进行扩展。比较常见的是对SWT Win32 API进行扩展,因为Windows的图形特性太丰富了,不用白不用啊。
下面就以SWT win32 x86来演示一下这个扩展过程。扩展很简单,就是在swt的窗口上加一个自定义系统菜单,最终效果如下图:
 
首先要有SWT JNI源代码,在Eclipse plug-in目录下找到org.eclipse.swt.win32.win32.x86.source_3.X.X.vXXX.jar(xx为版本号),解压缩到c:\build\swt-jni,解完后在会发现有一堆h和c文件,其中比较重要的就是os, gdip, xpcom,wgl,awt,用途分别如下:
    - os: 主要的JNI,用来创建控件,事件处理等。
    
- gdip: 与windows的dc交互,提供swt的gc画图功能。
    
- xpcom: 供swt调用mozilla系列浏览器如firefox等。
    
- wgl: 与3D相关,一般都用不上。
    
- awt:在swt中调用awt,awt也是jni。 
了解swt源代码之后,接下来为build swt做准备。
    - 安装vc6,尝试过用高版本,但不支持。
    
- 安装platform SDK 2003 February版,下载地址  遵照安装说明将SDK解压缩安装。不要尝试更新版本,不支持。
    
- 安装jdk,这个无所谓,1.4, 1.5, 1.6都可以。
    
- 下载gecko-sdk(下载地址),请务必使用1.8,swt目前不完全支持1.9。 
所以的工具都安装或解压之后,在swt源代码目录下找到build.bat,在:X86 label部分做如下配置,其中配置的目录视你的安装而定:
 1 :X86
:X86
 2
 3 IF x.%DEV_TOOLS%==x. set DEV_TOOLS=c:\PROGRA~1
IF x.%DEV_TOOLS%==x. set DEV_TOOLS=c:\PROGRA~1
 4 call %DEV_TOOLS%\MICROS~2\vc98\bin\vcvars32.bat
call %DEV_TOOLS%\MICROS~2\vc98\bin\vcvars32.bat
 5 IF x.%MSSDK%==x. set MSSDK=%DEV_TOOLS%\MICROS~3
IF x.%MSSDK%==x. set MSSDK=%DEV_TOOLS%\MICROS~3
 6 call %MSSDK%\setenv /XP32 /RETAIL
call %MSSDK%\setenv /XP32 /RETAIL
 7 IF x.%OUTPUT_DIR%==x. set OUTPUT_DIR=..\out
IF x.%OUTPUT_DIR%==x. set OUTPUT_DIR=..\out
 8 IF x.%JAVA_HOME%==x. set JAVA_HOME=%DEV_TOOLS%\Java\jdk1.6.0_06
IF x.%JAVA_HOME%==x. set JAVA_HOME=%DEV_TOOLS%\Java\jdk1.6.0_06
 9 IF x.%XULRUNNER_SDK%==x. set XULRUNNER_SDK=C:\gecko-sdk
IF x.%XULRUNNER_SDK%==x. set XULRUNNER_SDK=C:\gecko-sdk
10 set XULRUNNER_MAKE=make_xulrunner
set XULRUNNER_MAKE=make_xulrunner
11 IF x.%1==x.x86 shift
IF x.%1==x.x86 shift
12 GOTO MAKE
GOTO MAKE 
如果你和我一样的用的是Sun的JDK,且版本是1.5或1.6,还须修改一下和build.bat同目录的make_win32.mak文件。
替换
AWT_LIBS   = "$(JAVA_HOME)\jre\bin\jawt.lib"
为
AWT_LIBS   = "$(JAVA_HOME)\lib\jawt.lib"
一旦配置完成后,就可以在命令行中运行 build.bat x86 来build swt了,如果你的build过程中出错,使用build x86 clean删除垃圾文件,然后再查明原因。如果没有错误的话,目录下会生成5个dll文件,分别是swt-awt-win32-XXXX.dll, swt-gdip-win32-XXXX.dll, swt-wgl-win32-XXXX.dll, swt-win32-XXXX.dll, swt-xulrunner-win32-XXXX.dll,其中XXXX是视你的Eclipse版本而定。
要实现我们想要的效果,还需针对SWT的消息处理程序(WndProc) 扩展一下OS,那就是在就在主窗口建立过程中候往系统菜单里面加一自定义项。在Win32中,窗口建立的消息为WM_CREATE(值为1)。为简单起见,这里只是覆盖了SWT的默认消息处理程序,它通过os.c中的 OS_NATIVE(DefWindowProcW)方法来实现的(在98以后的版本api都是unicode,所以只覆盖W版本)
改动之前:
 1 #ifndef NO_DefWindowProcW
#ifndef NO_DefWindowProcW
 2 JNIEXPORT jint JNICALL OS_NATIVE(DefWindowProcW)
JNIEXPORT jint JNICALL OS_NATIVE(DefWindowProcW)
 3 (JNIEnv *env, jclass that, jint arg0, jint arg1, jint arg2, jint arg3)
    (JNIEnv *env, jclass that, jint arg0, jint arg1, jint arg2, jint arg3)
 4

 {
{
 5 jint rc = 0;
    jint rc = 0;
 6 OS_NATIVE_ENTER(env, that, DefWindowProcW_FUNC);
    OS_NATIVE_ENTER(env, that, DefWindowProcW_FUNC);
 7 rc = (jint)DefWindowProcW((HWND)arg0, arg1, (WPARAM)arg2, (LPARAM)arg3);
    rc = (jint)DefWindowProcW((HWND)arg0, arg1, (WPARAM)arg2, (LPARAM)arg3);
 8 OS_NATIVE_EXIT(env, that, DefWindowProcW_FUNC);
    OS_NATIVE_EXIT(env, that, DefWindowProcW_FUNC);
 9 return rc;
    return rc;
10 }
}
11 #endif
#endif 
改动之后:
 1 #ifndef NO_DefWindowProcW
#ifndef NO_DefWindowProcW
 2
 3 #define ID_CUSTOM_MENU 32888
#define ID_CUSTOM_MENU 32888
 4
 5 const wchar_t* wcTitle=L"Hello";
const wchar_t* wcTitle=L"Hello";
 6 const wchar_t* wcContent=L"World";
const wchar_t* wcContent=L"World";
 7 const wchar_t* wcName = L"Bang";
const wchar_t* wcName = L"Bang";
 8
 9 LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
10

 {
{
11 int wmId, wmEvent;
    int wmId, wmEvent;
12 HMENU hSysMenu;
    HMENU hSysMenu;
13 switch (message)
    switch (message)
14
 
     {
{
15 case WM_CREATE:
    case WM_CREATE:
16 hSysMenu = GetSystemMenu(hWnd,FALSE);
        hSysMenu = GetSystemMenu(hWnd,FALSE);
17 InsertMenuW(hSysMenu,1,MF_BYPOSITION | MF_STRING,ID_CUSTOM_MENU,wcName);
        InsertMenuW(hSysMenu,1,MF_BYPOSITION | MF_STRING,ID_CUSTOM_MENU,wcName);
18 break;
        break;
19 case WM_DESTROY:
    case WM_DESTROY:
20 PostQuitMessage(0);
        PostQuitMessage(0);
21 break;
        break;
22 case WM_SYSCOMMAND:
    case WM_SYSCOMMAND:
23 wmId = LOWORD(wParam);
        wmId = LOWORD(wParam);
24 if (wmId==ID_CUSTOM_MENU)
        if (wmId==ID_CUSTOM_MENU)
25 MessageBoxW(hWnd,wcTitle,wcContent,MB_OK);
            MessageBoxW(hWnd,wcTitle,wcContent,MB_OK);
26 else
        else
27 return DefWindowProcW(hWnd, message, wParam, lParam);
            return DefWindowProcW(hWnd, message, wParam, lParam);
28 break;
        break;
29 default:
    default:
30 return DefWindowProcW(hWnd, message, wParam, lParam);
        return DefWindowProcW(hWnd, message, wParam, lParam);
31 }
    }
32 return 0;
    return 0;
33 }
}
34
35 JNIEXPORT jint JNICALL OS_NATIVE(DefWindowProcW)
JNIEXPORT jint JNICALL OS_NATIVE(DefWindowProcW)
36 (JNIEnv *env, jclass that, jint arg0, jint arg1, jint arg2, jint arg3)
    (JNIEnv *env, jclass that, jint arg0, jint arg1, jint arg2, jint arg3)
37

 {
{
38 jint rc = 0;
    jint rc = 0;
39 OS_NATIVE_ENTER(env, that, DefWindowProcW_FUNC);
    OS_NATIVE_ENTER(env, that, DefWindowProcW_FUNC);
40 rc = (jint)MyWndProc((HWND)arg0, arg1, (WPARAM)arg2, (LPARAM)arg3);
    rc = (jint)MyWndProc((HWND)arg0, arg1, (WPARAM)arg2, (LPARAM)arg3);
41 OS_NATIVE_EXIT(env, that, DefWindowProcW_FUNC);
    OS_NATIVE_EXIT(env, that, DefWindowProcW_FUNC);
42 return rc;
    return rc;
43 }
}
44
45 #endif
#endif 
改动的目的就是在系统菜单里面加了一个菜单项 "Bang",点击后会出现“Hello world” 对话框。
重新在swt源目录下运行build x86,生成的swt就是扩展以后的版本。
现在我们来测试一下效果,写一个如下所示的HelloWorld1.java,放到swt源目录下,先运行javac HelloWorld1.java,再运行java HelloWorld1。不用担心classpath与library path,build之后它们默认都在当前目录下。
 import org.eclipse.swt.widgets.*;
import org.eclipse.swt.widgets.*;


 public class HelloWorld1
public class HelloWorld1  {
{


 public static void main (String [] args)
public static void main (String [] args)  {
{
 Display display = new Display ();
    Display display = new Display ();
 Shell shell = new HelloWorld1 ().open (display);
    Shell shell = new HelloWorld1 ().open (display);

 while (!shell.isDisposed ())
    while (!shell.isDisposed ())  {
{
 if (!display.readAndDispatch ()) display.sleep ();
        if (!display.readAndDispatch ()) display.sleep ();
 }
    }
 display.dispose ();
    display.dispose ();
 }
}
 
    

 public Shell open (Display display)
public Shell open (Display display)  {
{
 Shell shell = new Shell (display);
    Shell shell = new Shell (display);
 shell.open ();
    shell.open ();
 return shell;
    return shell;
 }
}
 }
}
在窗口标题栏单击右键,点击出现的Bang菜单会有如下效果图:

这个例子虽然简单,但大致描述了扩展swt的过程,更复杂的扩展等待YOU来实现!