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
This commit is contained in:
@@ -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, "<XPCOMObject method '%s'>" % (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, "<XPCOMObject method>","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 "<XPCOM interface '%s'>" % (self._comobj_.IID.name,)
|
||||
return "<XPCOM object '%s'>" % (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 = "<unknown>"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user