March 06
比较Eclipse与Mozilla中的COM/XPCOM Java Wrapper技术
COM和XPCOM都是基于virtual table技术,是一种二进制的规范(在Windows平台上,XPCOM与COM做到了二进制兼容。所以Windows平台上的XPCOM控件同时也是 COM控件)。COM和XPCOM的规范本身都没有限制实现的语言,但由于C++的virtual table与COM/XPCOM的virtual table在二进制上兼容,所以C++成了COM/XPCOM世界中的首选语言。
基本COM/XPCOM支持
在Java中调用COM/XPCOM
Eclipse SWT
对每一个COM interface中的方法,在其对应的Java class中会有一个同名的方法,接收Java参数,大部分情况下,这个Java方法会直接调用一个native方法: org.eclipse.swt.internal.ole.win32.COM#VtblCall(...)。因为不同COM interface的不同的方法可能有不同的个数和类型的参数,所以在org.eclipse.swt.internal.ole.win32.COM中提供一组VtblCall(...)的实现,每一个接受不同个数和类型的参数。每个VtblCall方法的实现基本是一样的,因为在这个方法中的参数包括了底层COM对象的地址,以及方法在virtual table中的序号,所以这个方法只需把Java的参数组marshall成C++参数,然后用virtual table来调用即可。
public class IUnknown
{
int address;
public IUnknown(int address) {
this.address = address;
}
public int AddRef() {
return COM.VtblCall(1, address);
}
public int getAddress() {
return address;
}
public int QueryInterface(GUID riid, int ppvObject[]) {
return COM.VtblCall(0, address, riid, ppvObject);
}
public int Release() {
return COM.VtblCall(2, address);
}
}
JavaXPCOM
JavaXPCOM基于一套与Eclipse SWT不同的思路。在JavaXPCOM中,每一个XPCOM interface有一个对应的Java interface,注意这里是Java interface,而不是Java class。那么,在JavaXPCOM中怎么生成一个XPCOM对象的Java wrapper呢?在JavaXPCOM中,巧妙地使用了reflection。对每一个XPCOM对象,会生成一个
Proxy 来作为Java wrapper,这个Proxy对象实现XPCOM对象所实现的interface。然后这个Proxy把Java interface中的方法调用再delegate到一个JavaXPCOM提供的
XPCOMJavaProxy(实现
InvocationHandler)上。
这里有几个问题:1。系统根据一个XPCOM对象的指针,怎么知道这个XPCOM对象实现了什么XPCOM接口?再怎么根据这个XPCOM接口找到对应的 Java interface来生成Proxy?2。XPCOMJavaProxy怎么把一个Java调用再映射到底层的XPCOM调用上?
JavaXPCOM是这样实现的:
- 对每一个XPCOM对象的指针,知道其实现的interface的IID。
- 使用nsIInterfaceInfoManager来reflect 这个IID,得到这个interface的meta data(nsIInterfaceInfo)
- 将这个XPCOM对象的指针及nsIInterfaceInfo组合在一起,放在一个JavaXPCOMInterface的数据结构里。
- 用这个JavaXPCOMInterface结构的指针来构建XPCOMJavaProxy(java wrapper)。构建XPCOMJavaProxy对象时(XPCOMJavaProxy#createProxy(Class aInterface, long aXPCOMInterface))有两个参数,第一个为这个proxy实现的Java interface。这个Java interface的名字由"org.mozilla.xpcom" + (XPCOM interface name)得来。
- 当XPCOMJavaProxy上的方法被调用时,native code会得到方法名、参数数组以及JavaXPCOMInterface的指针。从JavaXPCOMInterface可以得到 nsIInterfaceInfo,通过nsIInterface里所包含的meta data,可以得到这个方法在virtual table中的位置。同时meta data还会包含信息说明每个参数的数据类型,根据这个信息,可以把每个参数marshall成一个nsXPTCVariant结构。
- 通过xptcall,就可以完成对virtual table中的方法的调用。
- 对方法调用的结果,可以再根据meta data来unmarshall成Java对象。如果某个out参数或return参数是一个XPCOM对象,在meta data中会描述这个参数的interface的IID,那么又可以象第一步一样来对其生成Java wrapper(XPCOMJavaProxy)(nsJavaXPCOMBindingUtils.cpp#GetNewOrUsedJavaObject)。
当然,实际的实现更复杂,比如说有一个global table来记录Java wrapper与native的JavaXPCOMInterface之间的关系以避免不必要的多次为同一XPCOM对象建立Java wrapper等。
在Java中实现COM/XPCOM组件(component)
前面讨论了怎么从Java中调用COM/XPCOM中的组件,接下来讨论怎么用Java语言来实现COM/XPCOM组件。
Eclipse SWT
这里我尝试用一个图来说明Eclipse SWT中的实现。
- SWT会在内存中构建一个native function table(see COMObject#Callbacks)。这个table的横坐标为virtual table中的index,纵坐标为参数的个数。
- 这个table中的每个function的实现会调用COMObject这个类中的对应的静态方法:callbackn(int[] callbackArgs),这里的callbackn表示的是callback0,callback1...。在SWT中, org.eclipse.swt.internal.Callback这个类实现了一套通用的从native code回调Java的机制。COMObject使用了这套机制。
- 在传给callbackn(int[] callbackArgs)的参数中,第一个是virtual table的地址,根据这个地址,系统可以根据一个全局的hashtable找到对应的COMObject实例。然后调用 COMObject#methodn(int[] args)方法。这里methodn不是静态方法。
- 缺省的methodn的实现会返回COM.E_NOTIMPL。用户程序可以override,来实现自己的逻辑。
- 再回过头来看怎么在Java中实现一个COM组件:实例化一个COMObject,并override对应的methodn方法。作为参数传给COMObject的constructor每个methodn会需要几个参数。COMObject的初始化函数会在内存中构建virtual table,并让每个virtual table entry指向对应的在native function table中的位置(这里需要使用每个method需要几个参数这个信息)
下面是一个例子,简单地在Java中实现了一个IUnknown
iUnknown = new COMObject(new int[]{2, 0, 0}){ /* 2,0,0 is the arg count for queryInterface/addRef/release */
int refCount = 0;
public int method0(int[] args) {return QueryInterface(args[0], args[1]);}
public int method1(int[] args) {return AddRef();}
public int method2(int[] args) {return Release();}
public int AddRef() { return ++refCount;}
public int Release() {
refCount--;
if (refCount == 0) {COMObject.this.dispose();}
return refCount;
}
protected int QueryInterface(int riid, int ppvObject) {
if (riid == 0 || ppvObject == 0)
return COM.E_NOINTERFACE;
GUID guid = new GUID();
COM.MoveMemory(guid, riid, GUID.sizeof);
if (COM.IsEqualGUID(guid, COM.IIDIUnknown)) {
COM.MoveMemory(ppvObject, new int[] {iUnknown.getAddress()}, 4);
AddRef();
return COM.S_OK;
}
COM.MoveMemory(ppvObject, new int[] {0}, 4);
return COM.E_NOINTERFACE;
}
};
实现了多个COM interface的Java对象要稍微复杂一点。一般是在一个Java对象中管理多个COMObject,这几个COMObject的AddRef/Release/QueryInterface这几个函数的实现统一实现。
JavaXPCOM
JavaXPCOM中的支持还是依赖了type information,这是有了这个依赖,JavaXPCOM中实现XPCOM组件要容易得多。在JavaXPCOM中,只需要这个Java对象实现了所需要实现的XPCOM interface所对应的Java interface即可。
- 当一个Java object作为参数传给某个XPCOM方法时,native code会通过这个方法的meta data,知道这个参数应该是一个XPCOM对象。
- native code会检查这个Java object是不是一个Java wrapper,如果是,那么可以直接从这个Java wrapper知道它所wrap的XPCOM对象。
- 接下来会检查是不是已经给这个Java object生成过stub,如果没有则生成一个nsJavaXPTCStub。nsJavaXPTCStub 会根据meta data生成virtual table,而且当virtual table中的方法被调用时,会根据meta data知道被调用方法的名字,再根据这个名字到Java object中通过reflect找到对应的Java方法并调用它。
另外JavaXPCOM的实现中还实现了reference management,这样在Java code中不再需要去实现如AddRef/Release,系统已经都管理好了。
总结
在这里我们比较了Eclipse SWT和JavaXPCOM中对基本COM/XPCOM的支持的基本原理。这里没有讨论更高级一些的应用,比如说scripting(automation)。
Eclipse SWT
优点:
- 结构简单。因为支持基于virtual table,所以支持几乎最底层的COM规范。高效。
- 可以用工具来自动生成新的COM interface的Java wrapper。因为每个Java wrapper class的结构基本一样。
缺点:
- 不支持Java的garbage collection。Developer必须记得调用IUnknown#Release()方法,否则如果COM对象的Java wrapper被gc了,有可能造成内存泄露。
- 只支持有限种COM#VtblCall(...)方法。如果有一个新的COM interface中的某方法,需要更多的参数,而系统提供的COM#VtblCall(...)方法中没有与之匹配的,则需要另外再提供native code。
- 用Java实现XPCOM组件复杂。
JavaXPCOM
优点:
- 每个XPCOM interface对应到Java中还是interface。
- 支持Java的garbase collection。在XPCOMJavaProxy中,重载了finalize()方法,所以Java programmer不需要再去调用Release。
- 增加新的XPCOM interface容易。只需在org.mozilla.xpcom这个package中增加相应的Java interface即可。
- Java interface可以通过工具自动生成。
- 用Java实现XPCOM组件非常简单。
缺点:
- 由于实现依赖nsIInterfaceInfoManager,也就依赖typelib。这样一来,方法调用不能象Eclipse SWT中一样直接转换为virtual table调用,效率要明显低一些。另外,只能支持那些支持typelib的interface。
- 虽然增加新的XPCOM interface容易,但这个interface必须放在org.mozilla.xpcom这个package中,不适合第三方扩充。
|
 |