SWT源码分析(五)
先看程序:
package com.edgar;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
class TestMultButton {
public static void main(String[] args) {
Display display = new Display();// 创建一个display对象。
final Shell shell = new Shell(display);// shell是程序的主窗体
shell.setText("Java应用程序"); // 设置主窗体的标题
shell.setSize(300, 300); // 设置主窗体的大小
Button b1 = new Button(shell,SWT.NONE);
b1.setText("按钮1");
b1.setBounds(100,50, 100, 50);
Button b2 = new Button(shell,SWT.NONE);
b2.setText("按钮2");
b2.setBounds(100,150, 100, 50);
shell.open(); // 打开主窗体
while (!shell.isDisposed()) { // 如果主窗体没有关闭则一直循环
if (!display.readAndDispatch()) { // 如果display不忙
display.sleep(); // 休眠
}
}
display.dispose(); // 销毁display
}
}
窗口中有2个按钮,运行效果如下:
从前面文章我们已经知道,在执行final Shell shell = new Shell(display);这行代码时,会调用Control类中的
createWidget()方法:
void createWidget () {
state |= DRAG_DETECT;
foreground = background = -1;
checkOrientation (parent);
createHandle ();
checkBackground ();
checkBuffered ();
checkComposited ();
register ();
subclass ();
setDefaultFont ();
checkMirrored ();
checkBorder ();
if ((state & PARENT_BACKGROUND) != 0) {
setBackground ();
}
}
可以看到,中间调用了一个register()方法:
void register () {
display.addControl (handle, this);
}
意思是把当前控件(this),加到了display对象中,handle是什么?handle中文是句柄的意思,句柄是某个资源在操作系统(Windows)中的一个标识符,相当于该资源的代号。handle是Control类中的一个变量,所以shell,button等子类也有该变量。addControl方法的代码如下:
void addControl (int /*long*/ handle, Control control) {
if (handle == 0) return;
if (freeSlot == -1) {
int length = (freeSlot = indexTable.length) + GROW_SIZE;
int [] newIndexTable = new int [length];
Control [] newControlTable = new Control [length];
System.arraycopy (indexTable, 0, newIndexTable, 0, freeSlot);
System.arraycopy (controlTable, 0, newControlTable, 0, freeSlot);
for (int i=freeSlot; i<length-1; i++) newIndexTable [i] = i + 1;
newIndexTable [length - 1] = -1;
indexTable = newIndexTable;
controlTable = newControlTable;
}
if (USE_PROPERTY) {
OS.SetProp (handle, SWT_OBJECT_INDEX, freeSlot + 1);
} else {
OS.SetWindowLongPtr (handle, OS.GWLP_USERDATA, freeSlot + 1);
}
int oldSlot = freeSlot;
freeSlot = indexTable [oldSlot];
indexTable [oldSlot] = -2;
controlTable [oldSlot] = control;
}
里面用到了Display中的一些变量:
Control [] controlTable;
int freeSlot;
static final boolean USE_PROPERTY = !OS.IsWinCE;
static {
if (USE_PROPERTY) {
SWT_OBJECT_INDEX = OS.GlobalAddAtom (new TCHAR (0, "SWT_OBJECT_INDEX", true)); //$NON-NLS-1$
} else {
SWT_OBJECT_INDEX = 0;
}
}
可见,display对象,保存着程序中所有控件(Control)的一个数组,还有一个USE_PROPERTY变量来标识当前系统是不是WINCE系统,对于普通机器来说,不是WINCE系统,所以USE_PROPERTY为true。SWT_OBJECT_INDEX 被赋予了一个值。这个值是什么,我们不关心。
回到addControl方法,前面是一些判断,数组的扩充操作,这里不关心。因为USE_PROPERTY为true。所以执行
OS.SetProp()方法,这个方法的作用就是为某个handle的某个属性赋值。在这里是把SWT_OBJECT_INDEX属性的值设置为freeSlot +1,freeSlot初始值为0,第一次SetProp时SWT_OBJECT_INDEX的属性
为1.可以把OS.SetProp(),理解为Java web中的request.setAttribute("xxxx",xxxx);给一个变量上绑定一些属性。
后面有 controlTable [oldSlot] = control;即把本次要“注册”的控件加入到控件数组中,第一个控件的下标为0 。
以此类推, Button b1 = new Button(shell,SWT.NONE); Button b2 = new Button(shell,SWT.NONE);
会把按钮b1,b2也放入display对象的controlTable 数组中,下标分别为1,2。(Button的构造方法中最终也会调用createWidget()方法
,在这就不贴代码了)。
通过以上观察,可以得出:程序中的每一个控件,都会在display的controlTable 数组中保存着。
在之前的文章中,我们还没有看到windows程序中的“窗口过程函数”,窗口过程函数是C程序中处理窗口消息的地方。前面说过,窗口过程函数是一个回调函数,是由操作系统调用的,对于SWT程序来说,情况就变成了操作系统(C程序)调用SWT(Java程序)。如何在C中调用Java,这又牵扯到了JNI。
前面提到过,创建Display对象时,会调用Display中的init()方法:
protected void init () {
super.init ();
......
/* Create the callbacks */
windowCallback = new Callback (this, "windowProc", 4); //$NON-NLS-1$
windowProc = windowCallback.getAddress ();
if (windowProc == 0) error (SWT.ERROR_NO_MORE_CALLBACKS);
/* Remember the current thread id */
threadId = OS.GetCurrentThreadId ();
/* Use the character encoding for the default locale */
windowClass = new TCHAR (0, WindowName + WindowClassCount, true);
windowShadowClass = new TCHAR (0, WindowShadowName + WindowClassCount, true);
windowOwnDCClass = new TCHAR (0, WindowOwnDCName + WindowClassCount, true);
WindowClassCount++;
/* Register the SWT window class */
int /*long*/ hHeap = OS.GetProcessHeap ();
int /*long*/ hInstance = OS.GetModuleHandle (null);
WNDCLASS lpWndClass = new WNDCLASS ();
lpWndClass.hInstance = hInstance;
lpWndClass.lpfnWndProc = windowProc;
lpWndClass.style = OS.CS_BYTEALIGNWINDOW | OS.CS_DBLCLKS;
lpWndClass.hCursor = OS.LoadCursor (0, OS.IDC_ARROW);
lpWndClass.lpszClassName = OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
OS.MoveMemory (lpWndClass.lpszClassName, windowOwnDCClass, byteCount);
OS.RegisterClass (lpWndClass);
OS.HeapFree (hHeap, 0, lpWndClass.lpszClassName);
/* Create the message only HWND */
hwndMessage = OS.CreateWindowEx (0,
windowClass,
null,
OS.WS_OVERLAPPED,
0, 0, 0, 0,
0,
0,
hInstance,
null);
String title = "SWT_Window_"+APP_NAME;
OS.SetWindowText(hwndMessage, new TCHAR(0, title, true));
messageCallback = new Callback (this, "messageProc", 4); //$NON-NLS-1$
messageProc = messageCallback.getAddress ();
if (messageProc == 0) error (SWT.ERROR_NO_MORE_CALLBACKS);
OS.SetWindowLongPtr (hwndMessage, OS.GWLP_WNDPROC, messageProc);
......
}
这个方法比较长,重点看:
/* Create the callbacks */
windowCallback = new Callback (this, "windowProc", 4); //$NON-NLS-1$
windowProc = windowCallback.getAddress ();
.......
WNDCLASS lpWndClass = new WNDCLASS ();
lpWndClass.hInstance = hInstance;
lpWndClass.lpfnWndProc = windowProc;
......
这里创建了一个Callback对象,"Callback"就是回调的意思。windowProc是Callback对象的地址,然后把windowProc赋值给了lpWndClass.lpfnWndProc ,在C中,lpWndClass.lpfnWndProc存放的就是窗口过程函数的地址(函数指针).
通过RegisterClass函数注册lpWndClass后,操作系统就知道该窗口的窗口过程函数了。
可见,在SWT中是通过Callback对象来模拟窗口过程函数的地址,是C和Java之间的“桥梁”。
如果进入Callback最后调用的构造函数:
public Callback (Object object, String method, int argCount, boolean isArrayBased, int /*long*/ errorResult) {
/* Set the callback fields */
this.object = object;
this.method = method;
this.argCount = argCount;
this.isStatic = object instanceof Class;
this.isArrayBased = isArrayBased;
this.errorResult = errorResult;
/* Inline the common cases */
if (isArrayBased) {
signature = SIGNATURE_N;
} else {
switch (argCount) {
case 0: signature = SIGNATURE_0; break; //$NON-NLS-1$
case 1: signature = SIGNATURE_1; break; //$NON-NLS-1$
case 2: signature = SIGNATURE_2; break; //$NON-NLS-1$
case 3: signature = SIGNATURE_3; break; //$NON-NLS-1$
case 4: signature = SIGNATURE_4; break; //$NON-NLS-1$
default:
signature = getSignature(argCount);
}
}
/* Bind the address */
address = bind (this, object, method, signature, argCount, isStatic, isArrayBased, errorResult);
}
前面是一些赋值,最后有一个bind函数,并且是一个native函数:
static native synchronized int /*long*/ bind (Callback callback, Object object, String method, String signature, int argCount, boolean isStatic, boolean isArrayBased, int /*long*/ errorResult);
去callback.c中看具体实现:
JNIEXPORT jintLong JNICALL Java_org_eclipse_swt_internal_Callback_bind
(JNIEnv *env, jclass that, jobject callbackObject, jobject object, jstring method, jstring signature, jint argCount, jboolean isStatic, jboolean isArrayBased, jintLong errorResult)
{
int i;
jmethodID mid = NULL;
jclass javaClass = that;
const char *methodString = NULL, *sigString = NULL;
if (jvm == NULL) (*env)->GetJavaVM(env, &jvm);
if (JNI_VERSION == 0) JNI_VERSION = (*env)->GetVersion(env);
if (!initialized) {
memset(&callbackData, 0, sizeof(callbackData));
initialized = 1;
}
......
}
这些都是什么啊?看不懂。。。。上网查了一下,很多都是C通过JNI调用Java的步骤。具体的我也不了解了。有兴趣的可以查一下。
我从网上找了一篇文章,也是讲SWT实现的,里面对这个Callback做了一些介绍:http://wenku.baidu.com/view/acbbf9b069dc5022aaea0044.html
既然没法从Callback内部实现继续分析了,就只能从代码中找那个窗口过程函数了。在Display中发现了一个windowProc(int /*long*/ hwnd, int /*long*/ msg, int /*long*/ wParam, int /*long*/ lParam)方法:
int /*long*/ windowProc (int /*long*/ hwnd, int /*long*/ msg, int /*long*/ wParam, int /*long*/ lParam) {
/*
* Feature in Windows. On Vista only, it is faster to
* compute and answer the data for the visible columns
* of a table when scrolling, rather than just return
* the data for each column when asked.
*/
if (columnVisible != null) {
if (msg == OS.WM_NOTIFY && hwndParent == hwnd) {
OS.MoveMemory (hdr, lParam, NMHDR.sizeof);
switch (hdr.code) {
case OS.LVN_GETDISPINFOA:
case OS.LVN_GETDISPINFOW: {
OS.MoveMemory (plvfi, lParam, NMLVDISPINFO.sizeof);
if (0 <= plvfi.iSubItem && plvfi.iSubItem < columnCount) {
if (!columnVisible [plvfi.iSubItem]) return 0;
}
break;
}
}
}
}
if ((int)/*64*/msg == TASKBARBUTTONCREATED) {
if (taskBar != null) {
TaskItem [] items = taskBar.items;
for (int i=0; i<items.length; i++) {
TaskItem item = items [i];
if (item != null && item.shell != null && item.shell.handle == hwnd) {
item.recreate ();
break;
}
}
}
}
/*
* Bug in Adobe Reader 7.0. For some reason, when Adobe
* Reader 7.0 is deactivated from within Internet Explorer,
* it sends thousands of consecutive WM_NCHITTEST messages
* to the control that is under the cursor. It seems that
* if the control takes some time to respond to the message,
* Adobe stops sending them. The fix is to detect this case
* and sleep.
*
* NOTE: Under normal circumstances, Windows will never send
* consecutive WM_NCHITTEST messages to the same control without
* another message (normally WM_SETCURSOR) in between.
*/
if ((int)/*64*/msg == OS.WM_NCHITTEST) {
if (hitCount++ >= 1024) {
try {Thread.sleep (1);} catch (Throwable t) {}
}
} else {
hitCount = 0;
}
if (lastControl != null && lastHwnd == hwnd) {
return lastControl.windowProc (hwnd, (int)/*64*/msg, wParam, lParam);
}
int index;
if (USE_PROPERTY) {
index = (int)/*64*/OS.GetProp (hwnd, SWT_OBJECT_INDEX) - 1;
} else {
index = (int)/*64*/OS.GetWindowLongPtr (hwnd, OS.GWLP_USERDATA) - 1;
}
if (0 <= index && index < controlTable.length) {
Control control = controlTable [index];
if (control != null) {
lastHwnd = hwnd;
lastControl = control;
return control.windowProc (hwnd, (int)/*64*/msg, wParam, lParam);
}
}
return OS.DefWindowProc (hwnd, (int)/*64*/msg, wParam, lParam);
}
这个方法和C程序中的窗口过程函数的签名是一样的,他会不会就是SWT中的窗口过程函数呢?我在这个方法第一行
if (columnVisible != null) 处加一个断点,然后运行本文一开始的TestMultButton程序,会发现程序多次执行到
这个windowProc方法,而且不是在Java里面调用的,第一次执行windowProc的上下文环境如下:
可见,调用Display.windowProc(int,int,int,int)之前调用的是OS.createWindowEx,这也说明了windowProc
是一个回调函数。windowProc的关键代码是这些:
if (lastControl != null && lastHwnd == hwnd) {
return lastControl.windowProc (hwnd, (int)/*64*/msg, wParam, lParam);
}
int index;
if (USE_PROPERTY) {
index = (int)/*64*/OS.GetProp (hwnd, SWT_OBJECT_INDEX) - 1;
} else {
index = (int)/*64*/OS.GetWindowLongPtr (hwnd, OS.GWLP_USERDATA) - 1;
}
if (0 <= index && index < controlTable.length) {
Control control = controlTable [index];
if (control != null) {
lastHwnd = hwnd;
lastControl = control;
return control.windowProc (hwnd, (int)/*64*/msg, wParam, lParam);
}
}
return OS.DefWindowProc (hwnd, (int)/*64*/msg, wParam, lParam);
先是判断lastControl和lastHwnd是不是null,lastControl和lastHwnd正是在下面赋的值,从上文已经知道USE_PROPETRY为true,前面在addControl()方法中是
OS.SetProp (handle, SWT_OBJECT_INDEX, freeSlot + 1);
现在是
index = (int)/*64*/OS.GetProp (hwnd, SWT_OBJECT_INDEX) - 1;
所以取到的index的值就是set的时候的freeslot的值,也就是当前控件 (hwnd是窗口的句柄,代表要处理消息的控件),
index的值就是hwnd对应的控件在controlTable数组中的下标。
如果index大于等于0,就找出这个控件,调用该控件的windowProc(int,int,int,int)方法。
其他情况,调用系统默认的窗口函数,来处理消息。
Control的一些子类重写了windowProc(int,int,int,int)方法:
比如Text:
int /*long*/ windowProc (int /*long*/ hwnd, int msg, int /*long*/ wParam, int /*long*/ lParam) {
if (msg == OS.EM_UNDO) {
int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
if ((bits & OS.ES_MULTILINE) == 0) {
LRESULT result = wmClipboard (OS.EM_UNDO, wParam, lParam);
if (result != null) return result.value;
return callWindowProc (hwnd, OS.EM_UNDO, wParam, lParam);
}
}
if (msg == Display.SWT_RESTORECARET) {
callWindowProc (hwnd, OS.WM_KILLFOCUS, 0, 0);
callWindowProc (hwnd, OS.WM_SETFOCUS, 0, 0);
return 1;
}
return super.windowProc (hwnd, msg, wParam, lParam);
}
所有的子类在最后都会有super.windowProc(hwnd,msg,wParam,lParam)来调用父类的windowProc方法。
所以windowProc的调用顺序是:先从最底的类调用,逐级向上,最后调用的是Control的windowProc方法:
int /*long*/ windowProc (int /*long*/ hwnd, int msg, int /*long*/ wParam, int /*long*/ lParam) {
LRESULT result = null;
switch (msg) {
case OS.WM_ACTIVATE: result = WM_ACTIVATE (wParam, lParam); break;
case OS.WM_CAPTURECHANGED: result = WM_CAPTURECHANGED (wParam, lParam); break;
case OS.WM_CHANGEUISTATE: result = WM_CHANGEUISTATE (wParam, lParam); break;
case OS.WM_CHAR: result = WM_CHAR (wParam, lParam); break;
......
case OS.WM_LBUTTONDBLCLK: result = WM_LBUTTONDBLCLK (wParam, lParam); break;
case OS.WM_LBUTTONDOWN: result = WM_LBUTTONDOWN (wParam, lParam); break;
......
case OS.WM_SYSKEYUP: result = WM_SYSKEYUP (wParam, lParam); break;
case OS.WM_TIMER: result = WM_TIMER (wParam, lParam); break;
......
case OS.WM_XBUTTONUP: result = WM_XBUTTONUP (wParam, lParam); break;
}
if (result != null) return result.value;
return callWindowProc (hwnd, msg, wParam, lParam);
}
我们又看到类似Windows C程序中的swtich-case语句了。根据不同的消息,会调用不同的方法处理。至于处理消息的过程,下篇文章中讲。
未完待续。