diff --git a/pyuno/Library_pyuno.mk b/pyuno/Library_pyuno.mk index 65b3855e49b7..4767632e62d1 100644 --- a/pyuno/Library_pyuno.mk +++ b/pyuno/Library_pyuno.mk @@ -46,6 +46,7 @@ $(eval $(call gb_Library_add_exception_objects,pyuno,\ pyuno/source/module/pyuno_adapter \ pyuno/source/module/pyuno_gc \ pyuno/source/module/pyuno_iterator \ + pyuno/source/module/pyuno_service_constructor \ )) # vim:set noet sw=4 ts=4: diff --git a/pyuno/source/module/pyuno_except.cxx b/pyuno/source/module/pyuno_except.cxx index fe55c4bf36cb..c3d66f84b4d5 100644 --- a/pyuno/source/module/pyuno_except.cxx +++ b/pyuno/source/module/pyuno_except.cxx @@ -19,6 +19,8 @@ #include "pyuno_impl.hxx" #include +#include +#include #include @@ -70,23 +72,16 @@ void raisePyExceptionWithAny( const css::uno::Any &anyExc ) } /// @throws RuntimeException -static PyRef createClass( const OUString & name, const Runtime &runtime ) +static PyRef createClassFromTypeDescription( + std::u16string_view name, typelib_TypeDescription* pType, const Runtime& runtime ) { - // assuming that this is never deleted ! - // note I don't have the knowledge how to initialize these type objects correctly ! - TypeDescription desc( name ); - if( ! desc.is() ) - { - throw RuntimeException( "pyuno.getClass: uno exception " + name + " is unknown" ); - } - - bool isStruct = desc.get()->eTypeClass == typelib_TypeClass_STRUCT; - bool isExc = desc.get()->eTypeClass == typelib_TypeClass_EXCEPTION; - bool isInterface = desc.get()->eTypeClass == typelib_TypeClass_INTERFACE; + bool isStruct = pType->eTypeClass == typelib_TypeClass_STRUCT; + bool isExc = pType->eTypeClass == typelib_TypeClass_EXCEPTION; + bool isInterface = pType->eTypeClass == typelib_TypeClass_INTERFACE; if( !isStruct && !isExc && ! isInterface ) { - throw RuntimeException( "pyuno.getClass: " + name + "is a " + - OUString::createFromAscii( typeClassToString( static_cast(desc.get()->eTypeClass)) ) + + throw RuntimeException( "pyuno.getClass: " + OUString::Concat(name) + "is a " + + OUString::createFromAscii( typeClassToString( static_cast(pType->eTypeClass)) ) + ", expected EXCEPTION, STRUCT or INTERFACE" ); } @@ -94,7 +89,7 @@ static PyRef createClass( const OUString & name, const Runtime &runtime ) PyRef base; if( isInterface ) { - typelib_InterfaceTypeDescription *pDesc = reinterpret_cast(desc.get()); + typelib_InterfaceTypeDescription *pDesc = reinterpret_cast(pType); if( pDesc->pBaseTypeDescription ) { base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, runtime ); @@ -106,7 +101,7 @@ static PyRef createClass( const OUString & name, const Runtime &runtime ) } else { - typelib_CompoundTypeDescription *pDesc = reinterpret_cast(desc.get()); + typelib_CompoundTypeDescription *pDesc = reinterpret_cast(pType); if( pDesc->pBaseTypeDescription ) { base = getClass( pDesc->pBaseTypeDescription->aBase.pTypeName, runtime ); @@ -183,6 +178,79 @@ static PyRef createClass( const OUString & name, const Runtime &runtime ) return ret; } +static PyRef createEmptyPyTypeForTypeDescription( + const css::uno::Reference& xType) +{ + PyRef ret( + PyObject_CallFunctionObjArgs( + reinterpret_cast(&PyType_Type), + ustring2PyString(xType->getName()).get(), + PyRef(PyTuple_New(0), SAL_NO_ACQUIRE).get(), // bases + PyRef(PyDict_New(), SAL_NO_ACQUIRE).get(), + nullptr), + SAL_NO_ACQUIRE); + + return ret; +} + +static PyRef createClassForService( + const css::uno::Reference& xService) +{ + PyRef ret = createEmptyPyTypeForTypeDescription(xService); + + // Set an attribute on the class for each of the constructors + for (const auto& xConstructor : xService->getConstructors()) + { + OUString sName = xConstructor->getName(); + + // The name is empty for the default constructor + if (sName.isEmpty()) + { + if (xConstructor->isDefaultConstructor()) + sName = "create"; + else + continue; + } + + PyObject_SetAttr( + ret.get(), + ustring2PyString(sName).get(), + PyUNO_service_constructor_new(xService, xConstructor).get()); + } + + return ret; +} + +/// @throws RuntimeException +static PyRef createClass( const OUString & name, const Runtime &runtime ) +{ + // assuming that this is never deleted ! + // note I don't have the knowledge how to initialize these type objects correctly ! + TypeDescription desc(name); + if (desc.is()) + return createClassFromTypeDescription(name, desc.get(), runtime); + + // If there’s no type description from the typelib then check if it’s a service using the type + // description manager. + css::uno::Any xType; + + try + { + xType = runtime.getImpl()->cargo->xTdMgr->getByHierarchicalName(name); + } + catch (css::container::NoSuchElementException&) + { + // This will flow through to throw a runtime exception below + } + + css::uno::Reference xService; + + if ((xType >>= xService) && xService.is()) + return createClassForService(xService); + + throw RuntimeException("pyuno.getClass: uno exception " + name + " is unknown"); +} + bool isInstanceOfStructOrException( PyObject *obj) { PyRef attr( diff --git a/pyuno/source/module/pyuno_impl.hxx b/pyuno/source/module/pyuno_impl.hxx index 9dab16285ef3..245cecbc7832 100644 --- a/pyuno/source/module/pyuno_impl.hxx +++ b/pyuno/source/module/pyuno_impl.hxx @@ -56,6 +56,8 @@ namespace com::sun::star::container { class XEnumeration; } namespace com::sun::star::container { class XHierarchicalNameAccess; } namespace com::sun::star::lang { class XSingleServiceFactory; } namespace com::sun::star::reflection { class XIdlReflection; } +namespace com::sun::star::reflection { class XServiceConstructorDescription; } +namespace com::sun::star::reflection { class XServiceTypeDescription2; } namespace com::sun::star::script { class XInvocation2; } namespace com::sun::star::script { class XInvocationAdapterFactory2; } namespace com::sun::star::script { class XTypeConverter; } @@ -179,6 +181,10 @@ PyRef PyUNO_callable_new ( const css::uno::Reference &xInv, const OUString &methodName ); +PyRef PyUNO_service_constructor_new( + const css::uno::Reference& xService, + const css::uno::Reference& xConstructor); + PyObject* PyUNO_Type_new (const char *typeName , css::uno::TypeClass t , const Runtime &r ); PyObject* PyUNO_Enum_new( const char *enumBase, const char *enumValue, const Runtime &r ); PyObject* PyUNO_char_new (sal_Unicode c , const Runtime &r); diff --git a/pyuno/source/module/pyuno_service_constructor.cxx b/pyuno/source/module/pyuno_service_constructor.cxx new file mode 100644 index 000000000000..6374b7092797 --- /dev/null +++ b/pyuno/source/module/pyuno_service_constructor.cxx @@ -0,0 +1,290 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ +#include "pyuno_impl.hxx" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pyuno +{ +namespace +{ +struct PyUNO_service_constructor_Internals +{ + css::uno::Reference xService; + css::uno::Reference xConstructor; + // The parameter types are lazily converted to TypeDescriptions when the constructor is first + // called. + bool bTypesInitialized = false; + std::vector xParamTypes; + // Whether the last parameter is a rest parameter + bool bHasRest = false; + + void ensureParamTypes(); +}; + +void PyUNO_service_constructor_Internals::ensureParamTypes() +{ + if (bTypesInitialized) + return; + + css::uno::Sequence> aParams + = xConstructor->getParameters(); + + for (const auto& xParameter : aParams) + xParamTypes.emplace_back(xParameter->getType()->getName()); + + bHasRest = aParams.getLength() > 0 && aParams[aParams.getLength() - 1]->isRestParameter(); + + bTypesInitialized = true; +} + +struct PyUNO_service_constructor +{ + PyObject_HEAD; + + PyUNO_service_constructor_Internals members; +}; + +void PyUNO_service_constructor_dealloc(PyObject* self) +{ + PyUNO_service_constructor* me = reinterpret_cast(self); + + // members was allocated with the placement new operator so we need to explicitly call the + // destructor + me->members.~PyUNO_service_constructor_Internals(); + + Py_TYPE(self)->tp_free(self); +} + +PyObject* PyUNO_service_constructor_call(PyObject* self, PyObject* args, + SAL_UNUSED_PARAMETER PyObject*) +{ + PyUNO_service_constructor* me = reinterpret_cast(self); + + me->members.ensureParamTypes(); + + Py_ssize_t nParams = PyTuple_Size(args); + + // context, fixed params + sal_Int32 nMinParams = 1 + me->members.xParamTypes.size(); + sal_Int32 nMaxParams; + + if (me->members.bHasRest) + { + // The last parameter can be empty + nMinParams--; + nMaxParams = SAL_MAX_INT32; + } + else + nMaxParams = nMinParams; + + if (nParams < nMinParams || nParams > nMaxParams) + { + OUString sConstructorName = me->members.xConstructor->getName(); + + if (sConstructorName.isEmpty() && me->members.xConstructor->isDefaultConstructor()) + sConstructorName = "create"; + + OUStringBuffer buf(me->members.xService->getName() + "::" + sConstructorName + + " requires "); + + if (me->members.bHasRest) + buf.append("at least "); + + buf.append(OUString::number(nMinParams) + " argument"); + + if (nMinParams > 1) + buf.append('s'); + + PyErr_SetString( + PyExc_AttributeError, + OUStringToOString(buf.makeStringAndClear(), RTL_TEXTENCODING_ASCII_US).getStr()); + + return nullptr; + } + + try + { + Runtime runtime; + css::uno::Any contextAny = runtime.pyObject2Any(PyTuple_GetItem(args, 0)); + css::uno::Reference xContext; + + if (!(contextAny >>= xContext) || !xContext.is()) + { + PyErr_SetString(PyExc_AttributeError, + "First argument to a service constructor must be an XComponentContext"); + return nullptr; + } + + css::uno::Sequence aParams(nParams - 1); + css::uno::Any* pParams = aParams.getArray(); + + for (sal_Int32 i = 0; i < nParams - 1; ++i) + { + css::uno::Any param = runtime.pyObject2Any(PyTuple_GetItem(args, i + 1)); + + // Use the type of this parameter or the last one if we’re building the rest parameters + std::size_t nConstructorParam + = std::min(std::size_t(i), me->members.xParamTypes.size() - 1); + typelib_TypeDescription* pDestType = me->members.xParamTypes[nConstructorParam].get(); + + // Try to coax the any to the right type. This is needed for example to allow passing + // None to as an interface reference + pParams[i] + = runtime.getImpl()->cargo->xTypeConverter->convertTo(param, pDestType->pWeakRef); + } + + css::uno::Reference xInterface + = xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + me->members.xService->getName(), aParams, xContext); + return runtime.any2PyObject(css::uno::Any(std::move(xInterface))).getAcquired(); + } + catch (const css::reflection::InvocationTargetException& e) + { + raisePyExceptionWithAny(e.TargetException); + } + catch (const css::script::CannotConvertException& e) + { + raisePyExceptionWithAny(css::uno::Any(e)); + } + catch (const css::lang::IllegalArgumentException& e) + { + raisePyExceptionWithAny(css::uno::Any(e)); + } + catch (const css::uno::RuntimeException& e) + { + raisePyExceptionWithAny(css::uno::Any(e)); + } + + return nullptr; +} + +PyTypeObject PyUNO_service_constructor_Type = { + PyVarObject_HEAD_INIT(nullptr, 0) "PyUNO_service_constructor", + sizeof(PyUNO_service_constructor), + 0, + PyUNO_service_constructor_dealloc, +#if PY_VERSION_HEX >= 0x03080000 + 0, // Py_ssize_t tp_vectorcall_offset +#else + nullptr, // printfunc tp_print +#endif + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + PyUNO_service_constructor_call, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + 0 +#if PY_VERSION_HEX >= 0x03040000 + , + nullptr +#if PY_VERSION_HEX >= 0x03080000 + , + nullptr // vectorcallfunc tp_vectorcall +#if PY_VERSION_HEX < 0x03090000 +#if defined __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + , + nullptr // tp_print +#if defined __clang__ +#pragma clang diagnostic pop +#endif +#endif +#if PY_VERSION_HEX >= 0x030C00A1 + , + 0 // tp_watched +#endif +#if PY_VERSION_HEX >= 0x030D00A4 + , + 0 // tp_versions_used +#endif +#endif +#endif +}; +} + +PyRef PyUNO_service_constructor_new( + const css::uno::Reference& xService, + const css::uno::Reference& xConstructor) +{ + OSL_ENSURE(xService.is(), "xService must be valid"); + OSL_ENSURE(xConstructor.is(), "xConstructor must be valid"); + + PyUNO_service_constructor* self + = PyObject_New(PyUNO_service_constructor, &PyUNO_service_constructor_Type); + if (self == nullptr) + return nullptr; // NULL == Error! + + // The memory for members was allocated above but we still need to call the constructor so we’ll + // use a placement new. + new (&self->members) PyUNO_service_constructor_Internals; + + self->members.xService = xService; + self->members.xConstructor = xConstructor; + + return PyRef(reinterpret_cast(self), SAL_NO_ACQUIRE); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/pyuno/source/module/uno.py b/pyuno/source/module/uno.py index c5b5b25fa3ad..5cb6e6de9385 100644 --- a/pyuno/source/module/uno.py +++ b/pyuno/source/module/uno.py @@ -384,7 +384,7 @@ def _uno_import(name, *optargs, **kwargs): failed = False try: - # check for structs, exceptions or interfaces + # check for structs, exceptions, interfaces or services d[class_name] = pyuno.getClass(name + "." + class_name) except RuntimeException: # check for enums