From 031bf2a05ca9b5070333a3717df458f7bc5d2a84 Mon Sep 17 00:00:00 2001
From: "markh%activestate.com"
Date: Sun, 27 May 2001 02:51:18 +0000
Subject: [PATCH] Add interface flattening to Python XPCOM bindings.
Not part of the build, but a=drivers@mozilla.org anyway!
git-svn-id: svn://10.0.0.236/trunk@95969 18797224-902f-48f8-a5cc-f745e15eee43
---
.../python/xpcom/client/__init__.py | 289 +++++++++++++-----
mozilla/extensions/python/xpcom/components.py | 3 +-
.../extensions/python/xpcom/doc/tutorial.html | 16 +-
.../python/xpcom/makefile.stupid.linux | 1 +
mozilla/extensions/python/xpcom/readme.html | 9 +-
.../python/xpcom/server/__init__.py | 4 +-
.../extensions/python/xpcom/server/policy.py | 85 ++++--
.../extensions/python/xpcom/src/PyGBase.cpp | 2 +-
.../python/xpcom/src/PyIClassInfo.cpp | 159 ++++++++++
.../python/xpcom/src/PyIComponentManager.cpp | 2 +-
.../python/xpcom/src/PyIInterfaceInfo.cpp | 2 +-
.../python/xpcom/src/PyISupports.cpp | 21 +-
mozilla/extensions/python/xpcom/src/PyXPCOM.h | 93 +++++-
.../extensions/python/xpcom/src/PyXPCOM_std.h | 19 +-
.../python/xpcom/src/TypeObject.cpp | 11 +-
.../python/xpcom/src/loader/pyloader.cpp | 3 +
.../extensions/python/xpcom/src/makefile.win | 2 +
mozilla/extensions/python/xpcom/src/xpcom.cpp | 79 ++---
.../python/xpcom/test/output/test_misc | 9 +-
.../python/xpcom/test/test_components.py | 4 +-
.../xpcom/test/test_isupports_primitives.py | 2 +-
.../extensions/python/xpcom/test/test_misc.py | 20 +-
.../python/xpcom/test/test_test_component.py | 12 +-
mozilla/extensions/python/xpcom/xpt.py | 1 +
24 files changed, 649 insertions(+), 199 deletions(-)
create mode 100644 mozilla/extensions/python/xpcom/src/PyIClassInfo.cpp
diff --git a/mozilla/extensions/python/xpcom/client/__init__.py b/mozilla/extensions/python/xpcom/client/__init__.py
index b54d5c7eb7b..2787910e137 100644
--- a/mozilla/extensions/python/xpcom/client/__init__.py
+++ b/mozilla/extensions/python/xpcom/client/__init__.py
@@ -17,9 +17,11 @@
import os
import new
-from xpcom import xpt, _xpcom, COMException, nsError
+from xpcom import xpt, COMException, nsError
-XPTC_InvokeByIndex = _xpcom.XPTC_InvokeByIndex
+# Suck in stuff from _xpcom we use regularly to prevent a module lookup
+from xpcom._xpcom import IID_nsISupports, IID_nsIClassInfo, IID_nsISupportsString, IID_nsISupportsWeakReference, \
+ IID_nsIWeakReference, XPTI_GetInterfaceInfoManager, NS_GetGlobalComponentManager, XPTC_InvokeByIndex
_just_int_interfaces = ["nsISupportsPRInt32", "nsISupportsPRInt16", "nsISupportsPRUint32", "nsISupportsPRUint16", "nsISupportsPRUint8", "nsISupportsPRBool"]
_just_long_interfaces = ["nsISupportsPRInt64", "nsISupportsPRUint64"]
@@ -31,9 +33,7 @@ _float_interfaces = _just_float_interfaces + _just_long_interfaces + _just_int_i
method_template = """
def %s(self, %s):
- return XPTC_InvokeByIndex(self._comobj_, %d,
- (%s,
- (%s)))
+ return XPTC_InvokeByIndex(self._comobj_, %d, (%s, (%s)))
"""
def _MakeMethodCode(method):
# Build a declaration
@@ -66,8 +66,43 @@ def _MakeMethodCode(method):
# Keyed by IID, each item is a tuple of (methods, getters, setters)
interface_cache = {}
+# Keyed by [iid][name], each item is an unbound method.
+interface_method_cache = {}
-# Fully process the interface - generate method code, etc.
+def _shutdown():
+ interface_cache.clear()
+ interface_method_cache.clear()
+
+# Fully process the named method, generating method code etc.
+def BuildMethod(method_info, iid):
+ name = method_info.name
+ try:
+ return interface_method_cache[iid][name]
+ except KeyError:
+ pass
+ # Generate it.
+ assert not (method_info.IsSetter() or method_info.IsGetter()), "getters and setters should have been weeded out by now"
+ method_code = _MakeMethodCode(method_info)
+ # Build the method - We only build a function object here
+ # - they are bound to each instance as needed.
+
+## print "Method Code for %s (%s):" % (name, iid)
+## print method_code
+ codeObject = compile(method_code, "" % (name,), "exec")
+ # Exec the code object
+ tempNameSpace = {}
+ exec codeObject in globals(), tempNameSpace
+ ret = tempNameSpace[name]
+ if not interface_method_cache.has_key(iid):
+ interface_method_cache[iid] = {}
+ interface_method_cache[iid][name] = ret
+ return ret
+
+from xpcom.xpcom_consts import XPT_MD_GETTER, XPT_MD_SETTER, XPT_MD_NOTXPCOM, XPT_MD_CTOR, XPT_MD_HIDDEN
+FLAGS_TO_IGNORE = XPT_MD_NOTXPCOM | XPT_MD_CTOR | XPT_MD_HIDDEN
+
+# Pre-process the interface - generate a list of methods, constants etc,
+# but don't actually generate the method code.
def BuildInterfaceInfo(iid):
ret = interface_cache.get(iid, None)
if ret is None:
@@ -75,72 +110,30 @@ def BuildInterfaceInfo(iid):
method_code_blocks = []
getters = {}
setters = {}
- method_names = []
+ method_infos = {}
interface = xpt.Interface(iid)
for m in interface.methods:
- if not m.IsNotXPCOM() and \
- not m.IsHidden() and \
- not m.IsConstructor():
- # Yay - a method we can use!
- if m.IsSetter():
+ flags = m.flags
+ if flags & FLAGS_TO_IGNORE == 0:
+ if flags & XPT_MD_SETTER:
param_flags = map(lambda x: (x.param_flags,) + xpt.MakeReprForInvoke(x), m.params)
setters[m.name] = m.method_index, param_flags
- elif m.IsGetter():
+ elif flags & XPT_MD_GETTER:
param_flags = map(lambda x: (x.param_flags,) + xpt.MakeReprForInvoke(x), m.params)
getters[m.name] = m.method_index, param_flags
else:
- method_names.append(m.name)
- method_code_blocks.append(_MakeMethodCode(m))
+ method_infos[m.name] = m
# Build the constants.
constants = {}
for c in interface.constants:
constants[c.name] = c.value
- # Build the methods - We only build function objects here
- # - they are bound to each instance at instance creation.
- methods = {}
- if len(method_code_blocks)!=0:
- method_code = "\n".join(method_code_blocks)
-## print "Method Code:"
-## print method_code
- codeObject = compile(method_code, "","exec")
- # Exec the code object
- tempNameSpace = {}
- exec codeObject in globals(), tempNameSpace # self.__dict__, self.__dict__
- for name in method_names:
- methods[name] = tempNameSpace[name]
- ret = methods, getters, setters, constants
+ ret = method_infos, getters, setters, constants
interface_cache[iid] = ret
return ret
-def Component(ob, iid):
- ob_name = None
- if not hasattr(ob, "IID"):
- ob_name = ob
- cm = _xpcom.NS_GetGlobalComponentManager()
- ob = cm.CreateInstanceByContractID(ob)
- return Interface(ob, iid)
-
-class Interface:
- """Implements a dynamic interface using xpcom reflection.
- """
- def __init__(self, ob, iid, object_name = None):
- ob = ob.QueryInterface(iid, 0) # zero means "no auto-wrap"
- self.__dict__['_comobj_'] = ob
- # Hack - the last interface only!
- methods, getters, setters, constants = BuildInterfaceInfo(iid)
- self.__dict__['_interface_infos_'] = getters, setters
- self.__dict__['_interface_methods_'] = methods # Unbound methods.
-
- self.__dict__.update(constants)
- # We remember the constant names to prevent the user trying to assign to them!
- self.__dict__['_constant_names_'] = constants.keys()
-
- if object_name is None:
- object_name = "object with interface '%s'" % (iid.name,)
- self.__dict__['_object_name_'] = object_name
-
+class _XPCOMBase:
def __cmp__(self, other):
try:
other = other._comobj_
@@ -152,19 +145,19 @@ class Interface:
return hash(self._comobj_)
def __repr__(self):
- return "" % (self._comobj_.IID.name,)
+ return "" % (self._object_name_,)
# See if the object support strings.
def __str__(self):
try:
- self._comobj_.QueryInterface(_xpcom.IID_nsISupportsString)
+ self._comobj_.QueryInterface(IID_nsISupportsString, 0)
return str(self._comobj_)
except COMException:
return self.__repr__()
# Try the numeric support.
def _do_conversion(self, interface_names, cvt):
- iim = _xpcom.XPTI_GetInterfaceInfoManager()
+ iim = XPTI_GetInterfaceInfoManager()
for interface_name in interface_names:
iid = iim.GetInfoForName(interface_name).GetIID()
try:
@@ -182,6 +175,139 @@ class Interface:
def __float__(self):
return self._do_conversion(_float_interfaces, float)
+
+class Component(_XPCOMBase):
+ def __init__(self, ob, iid):
+ assert not hasattr(ob, "_comobj_"), "Should be a raw nsIWhatever, not a wrapped one"
+ ob_name = None
+ if not hasattr(ob, "IID"):
+ ob_name = ob
+ cm = NS_GetGlobalComponentManager()
+ ob = cm.CreateInstanceByContractID(ob)
+ assert not hasattr(ob, "_comobj_"), "The created object should be a raw nsIWhatever, not a wrapped one"
+ # Keep a reference to the object in the component too
+ self.__dict__['_comobj_'] = ob
+ # hit __dict__ directly to avoid __setattr__()
+ self.__dict__['_interfaces_'] = {} # keyed by IID
+ self.__dict__['_interface_names_'] = {} # keyed by IID name
+ self.__dict__['_interface_infos_'] = {} # keyed by IID
+ self.__dict__['_name_to_interface_name_'] = {}
+ self.__dict__['_tried_classinfo_'] = 0
+
+ if ob_name is None:
+ ob_name = ""
+ self.__dict__['_object_name_'] = ob_name
+ self.QueryInterface(iid)
+
+ def _build_all_supported_interfaces_(self):
+ # Use nsIClassInfo, but don't do it at object construction to keep perf up.
+ # Only pay the penalty when we really need it.
+ assert not self._tried_classinfo_, "already tried to get the class info."
+ self.__dict__['_tried_classinfo_'] = 1
+ # See if nsIClassInfo is supported.
+ try:
+ classinfo = self._comobj_.QueryInterface(IID_nsIClassInfo, 0)
+ except COMException:
+ classinfo = None
+ if classinfo is not None:
+# print "YAY - have class info!!", classinfo
+ real_cid = classinfo.contractID
+ if real_cid is not None:
+ self.__dict__['_object_name_'] = real_cid
+ for nominated_iid in classinfo.getInterfaces():
+ self._remember_interface_info(nominated_iid)
+ self.__dict__['_com_classinfo_'] = classinfo
+
+ def _remember_interface_info(self, iid):
+ method_infos, getters, setters, constants = BuildInterfaceInfo(iid)
+ # Remember all the names so we can delegate
+ assert not self.__dict__['_interface_infos_'].has_key(iid), "Already remembered this interface!"
+ self.__dict__['_interface_infos_'][iid] = method_infos, getters, setters, constants
+ interface_name = iid.name
+ names = self.__dict__['_name_to_interface_name_']
+ for name in method_infos.keys(): names[name] = interface_name
+ for name in getters.keys(): names[name] = interface_name
+ for name in setters.keys(): names[name] = interface_name
+ for name in constants.keys(): names[name] = interface_name
+
+ def _make_interface_info(self, ob, iid):
+ interface_infos = self._interface_infos_
+ assert not self._interfaces_.has_key(iid), "Already have made this interface"
+ method_infos, getters, setters, constants = interface_infos[iid]
+ new_interface = _Interface(ob, iid, method_infos, getters, setters, constants)
+ self._interfaces_[iid] = new_interface
+ self._interface_names_[iid.name] = new_interface
+ # No point remembering these.
+ del interface_infos[iid]
+
+ def QueryInterface(self, iid):
+ if self._interfaces_.has_key(iid):
+ assert self._interface_names_.has_key(iid.name), "_interfaces_ has the key, but _interface_names_ does not!"
+ return self
+ # Haven't seen this before - do a real QI.
+ if not self._interface_infos_.has_key(iid):
+ self._remember_interface_info(iid)
+ ret = self._comobj_.QueryInterface(iid, 0)
+# print "Component QI for", iid, "yielded", ret
+ self._make_interface_info(ret, iid)
+ assert self._interfaces_.has_key(iid) and self._interface_names_.has_key(iid.name), "Making the interface didn't update the maps"
+ return self
+
+ queryInterface = QueryInterface # Alternate name.
+
+ def __getattr__(self, attr):
+ # First allow the interface name to return the "raw" interface
+ interface = self.__dict__['_interface_names_'].get(attr, None)
+ if interface is not None:
+ return interface
+ interface_name = self.__dict__['_name_to_interface_name_'].get(attr, None)
+ # This may be first time trying this interface - get the nsIClassInfo
+ if interface_name is None and not self._tried_classinfo_:
+ self._build_all_supported_interfaces_()
+ interface_name = self.__dict__['_name_to_interface_name_'].get(attr, None)
+
+ if interface_name is not None:
+ interface = self.__dict__['_interface_names_'].get(interface_name, None)
+ if interface is None:
+ iid = XPTI_GetInterfaceInfoManager().GetInfoForName(interface_name).GetIID()
+ self.QueryInterface(iid)
+ interface = self.__dict__['_interface_names_'][interface_name]
+ return getattr(interface, attr)
+ # Some interfaces may provide this name via "native" support.
+ # Loop over all interfaces, and if found, cache it for next time.
+ for interface in self.__dict__['_interfaces_'].values():
+ try:
+ ret = getattr(interface, attr)
+ self.__dict__['_name_to_interface_name_'][attr] = interface._iid_.name
+ return ret
+ except AttributeError:
+ pass
+ raise AttributeError, "XPCOM component '%s' has no attribute '%s'" % (self._object_name_, attr)
+
+ def __setattr__(self, attr, val):
+ interface_name = self._name_to_interface_name_.get(attr, None)
+ # This may be first time trying this interface - get the nsIClassInfo
+ if interface_name is None and not self._tried_classinfo_:
+ self._build_all_supported_interfaces_()
+ interface_name = self.__dict__['_name_to_interface_name_'].get(attr, None)
+ if interface_name is not None:
+ interface = self._interface_names_[interface_name]
+ setattr(interface, attr, val)
+ return
+ raise AttributeError, "XPCOM component '%s' can not set attribute '%s'" % (self._object_name_, attr)
+
+class _Interface(_XPCOMBase):
+ def __init__(self, comobj, iid, method_infos, getters, setters, constants):
+ self.__dict__['_comobj_'] = comobj
+ self.__dict__['_iid_'] = iid
+ self.__dict__['_property_getters_'] = getters
+ self.__dict__['_property_setters_'] = setters
+ self.__dict__['_method_infos_'] = method_infos # method infos waiting to be turned into real methods.
+ self.__dict__['_methods_'] = {} # unbound methods
+ self.__dict__['_object_name_'] = iid.name
+ self.__dict__.update(constants)
+ # We remember the constant names to prevent the user trying to assign to them!
+ self.__dict__['_constant_names_'] = constants.keys()
def __getattr__(self, attr):
# Allow the underlying interface to provide a better implementation if desired.
@@ -189,19 +315,29 @@ class Interface:
if ret is not None:
return ret
# Do the function thing first.
- unbound_method = self.__dict__['_interface_methods_'].get(attr)
+ unbound_method = self.__dict__['_methods_'].get(attr, None)
if unbound_method is not None:
return new.instancemethod(unbound_method, self, self.__class__)
- getters, setters = self.__dict__['_interface_infos_']
+ getters = self.__dict__['_property_getters_']
info = getters.get(attr)
- if info is None:
- raise AttributeError, "XPCOM component '%s' has no attribute '%s'" % (self._object_name_, attr)
- method_index, param_infos = info
- if len(param_infos)!=1: # Only expecting a retval
- raise RuntimeError, "Can't get properties with this many args!"
- args = ( param_infos, () )
- return XPTC_InvokeByIndex(self._comobj_, method_index, args)
+ if info is not None:
+ method_index, param_infos = info
+ if len(param_infos)!=1: # Only expecting a retval
+ raise RuntimeError, "Can't get properties with this many args!"
+ args = ( param_infos, () )
+ return XPTC_InvokeByIndex(self, method_index, args)
+
+ # See if we have a method info waiting to be turned into a method.
+ # Do this last as it is a one-off hit.
+ method_info = self.__dict__['_method_infos_'].get(attr, None)
+ if method_info is not None:
+ unbound_method = BuildMethod(method_info, self._iid_)
+ # Cache it locally
+ self.__dict__['_methods_'][attr] = unbound_method
+ return new.instancemethod(unbound_method, self, self.__class__)
+
+ raise AttributeError, "XPCOM component '%s' has no attribute '%s'" % (self._object_name_, attr)
def __setattr__(self, attr, val):
# If we already have a __dict__ item of that name, and its not one of
@@ -210,7 +346,7 @@ class Interface:
self.__dict__[attr] = val
return
# Start sniffing for what sort of attribute this might be?
- getters, setters = self.__dict__['_interface_infos_']
+ setters = self.__dict__['_property_setters_']
info = setters.get(attr)
if info is None:
raise AttributeError, "XPCOM component '%s' can not set attribute '%s'" % (self._object_name_, attr)
@@ -218,12 +354,13 @@ class Interface:
if len(param_infos)!=1: # Only expecting a single input val
raise RuntimeError, "Can't set properties with this many args!"
real_param_infos = ( param_infos, (val,) )
- return XPTC_InvokeByIndex(self._comobj_, method_index, real_param_infos)
+ return XPTC_InvokeByIndex(self, method_index, real_param_infos)
+
# Called by the _xpcom C++ framework to wrap interfaces up just
# before they are returned.
def MakeInterfaceResult(ob, iid):
- return Interface(ob, iid)
+ return Component(ob, iid)
class WeakReference:
"""A weak-reference object. You construct a weak reference by passing
@@ -233,7 +370,7 @@ class WeakReference:
Once you have a weak-reference, you can "call" the object to get
back a strong reference. Eg:
- >>> some_ob = components.classes['...]
+ >>> some_ob = components.classes['...']
>>> weak_ref = WeakReference(some_ob)
>>> new_ob = weak_ref() # new_ob is effectively "some_ob" at this point
>>> # EXCEPT: new_ob may be None of some_ob has already died - a
@@ -243,18 +380,18 @@ class WeakReference:
or else you defeat the purpose of the weak-reference.
"""
def __init__(self, ob, iid = None):
- swr = Interface(ob, _xpcom.IID_nsISupportsWeakReference)
- self._comobj_ = Interface(swr.GetWeakReference(), _xpcom.IID_nsIWeakReference)
+ swr = Component(ob._comobj_, IID_nsISupportsWeakReference)
+ self._comobj_ = Component(swr.GetWeakReference()._comobj_, IID_nsIWeakReference)
if iid is None:
try:
iid = ob.IID
except AttributeError:
- iid = _xpcom.IID_nsISupports
+ iid = IID_nsISupports
self._iid_ = iid
def __call__(self, iid = None):
if iid is None: iid = self._iid_
try:
- return Interface(self._comobj_.QueryReferent(iid), iid)
+ return Component(self._comobj_.QueryReferent(iid)._comobj_, iid)
except COMException, details:
if details.errno != nsError.NS_ERROR_NULL_POINTER:
raise
diff --git a/mozilla/extensions/python/xpcom/components.py b/mozilla/extensions/python/xpcom/components.py
index 4e7318bf63f..16a82e5076b 100644
--- a/mozilla/extensions/python/xpcom/components.py
+++ b/mozilla/extensions/python/xpcom/components.py
@@ -32,7 +32,7 @@ def _get_good_iid(iid):
return iid
# The "manager" object.
-manager = xpcom.client.Interface(_xpcom.NS_GetGlobalComponentManager(), _xpcom.IID_nsIComponentManager)
+manager = xpcom.client.Component(_xpcom.NS_GetGlobalComponentManager(), _xpcom.IID_nsIComponentManager)
# The "interfaceInfoManager" object - JS doesnt have this.
interfaceInfoManager = _xpcom.XPTI_GetInterfaceInfoManager()
@@ -190,6 +190,7 @@ class _ShutdownObserver:
global interfaceInfoManager
global _shutdownObserver
manager = interfaceInfoManager = _shutdownObserver = None
+ xpcom.client._shutdown()
svc = _xpcom.GetGlobalServiceManager().GetService("@mozilla.org/observer-service;1", interfaces.nsIObserverService)
# Observers will be QI'd for a weak-reference, so we must keep the
diff --git a/mozilla/extensions/python/xpcom/doc/tutorial.html b/mozilla/extensions/python/xpcom/doc/tutorial.html
index 6fac965b4e6..78deac85ce8 100644
--- a/mozilla/extensions/python/xpcom/doc/tutorial.html
+++ b/mozilla/extensions/python/xpcom/doc/tutorial.html
@@ -74,7 +74,7 @@ and implements the nsISample interface.
Thus, a complete Python program that uses this component is shown below.
from xpcom import components
cls = components.classes["@mozilla.org/sample;1"]
-ob = cls.createInstance(components.interfaces.nsISample)
+ob = cls.createInstance() # no need to specify an IID for most components
# nsISample defines a "value" property - let's use it!
ob.value = "new value"
if ob.value != "new value":
@@ -233,6 +233,20 @@ any given XPCOM method, there is only one possible type for a given parameter.
these parameters; in contrast, JavaScript requires these redundant parameters.
+Interface Flattening
+Most people can ignore this information - Python XPCOM objects just
+work. However, if you are familiar with xpcom from C++ and the concept of QueryInterface,
+you may like to read this.
+Most components support the concept of "interface
+flattening". Such objects can report the interfaces they support,
+allowing languages such as Python and Javascript avoid using QueryInterface.
+When you are using an XPCOM object from Python, you can just call methods and
+reference properties without regard for the interface that implements it.
+When multiple interfaces share the same method or property name, you can use
+the name of the interface as a differentiator. Thus, ob.nsIFoo.close()
+will call close on ob's nsIFoo interface, while ob.nsIBar.close()
+will use the nsIBar interface. ob.close() is not defined.
+