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语句了。根据不同的消息,会调用不同的方法处理。至于处理消息的过程,下篇文章中讲。

 

未完待续。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值