From 6154aa313cc198dc9426e792d23411ba56c7c11d Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Wed, 26 Aug 2026 14:15:50 +0200 Subject: [PATCH] Related tdf#173293 comphelper: Add helper class for XInvocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lots of classes implement XInvocation in order to add extra methods and properties that are determined at runtime. One problem with this is that when such an object is introspected with the css.script.Invocation service then that service uses the XInvocation interface directly to query what methods and properties are supported instead of creating an XIntrospectionAccess via the css.beans.Introspection service. It looks like probably all of the current implementations of XInvocation only use it to advertise the dynamic methods and don’t advertise any concrete methods provided by any interfaces that the object implements. This means for example that when the object is accessed by pyuno, only the dynamic methods and properties can be used. This patch adds a helper class that can be used to help implement XInvocation in such a way that if a requested method isn’t found as a dynamic method, it can pass the method call on to the helper which will use the normal Introspection service to query the available methods and properties. Change-Id: Ia4363594ee274c36249a0d1c94f16a829ace4647 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/210130 Tested-by: Jenkins Reviewed-by: Neil Roberts --- comphelper/Library_comphelper.mk | 1 + comphelper/source/misc/invocationhelper.cxx | 243 ++++++++++++++++++++ include/comphelper/invocationhelper.hxx | 89 +++++++ 3 files changed, 333 insertions(+) create mode 100644 comphelper/source/misc/invocationhelper.cxx create mode 100644 include/comphelper/invocationhelper.hxx diff --git a/comphelper/Library_comphelper.mk b/comphelper/Library_comphelper.mk index 4939cda63aba..eb27e52361cf 100644 --- a/comphelper/Library_comphelper.mk +++ b/comphelper/Library_comphelper.mk @@ -117,6 +117,7 @@ $(eval $(call gb_Library_add_exception_objects,comphelper,\ comphelper/source/misc/hash \ comphelper/source/misc/instancelocker \ comphelper/source/misc/interaction \ + comphelper/source/misc/invocationhelper \ comphelper/source/misc/logging \ comphelper/source/misc/lok \ comphelper/source/misc/mimeconfighelper \ diff --git a/comphelper/source/misc/invocationhelper.cxx b/comphelper/source/misc/invocationhelper.cxx new file mode 100644 index 000000000000..90f532d891dd --- /dev/null +++ b/comphelper/source/misc/invocationhelper.cxx @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace comphelper +{ +constexpr sal_Int32 ALLOWED_METHODS + = css::beans::MethodConcept::ALL ^ css::beans::MethodConcept::DANGEROUS; +constexpr sal_Int32 ALLOWED_PROPERTIES + = css::beans::PropertyConcept::ALL ^ css::beans::PropertyConcept::DANGEROUS; + +InvocationHelper::InvocationHelper(const css::uno::Reference& xContext) + : m_xContext(xContext) +{ +} + +css::uno::Reference +InvocationHelper::getIntrospection(css::script::XInvocation* pInvocation) +{ + ensureIntrospection(pInvocation); + + return m_xIntrospectionAccess; +} + +css::uno::Sequence InvocationHelper::prepareInvokeParams( + const css::uno::Sequence& aParams, + const css::uno::Sequence& aParamInfos) +{ + css::uno::Sequence aInvokeParams(aParamInfos.getLength()); + css::uno::Any* pInvokeParams = aInvokeParams.getArray(); + + for (sal_Int32 nParam = 0, nParams = aParamInfos.getLength(); nParam < nParams; ++nParam) + { + const css::reflection::ParamInfo& rParam = aParamInfos[nParam]; + + if (rParam.aMode == css::reflection::ParamMode_OUT) + { + // Initialize default value if the parameter is OUT + rParam.aType->createObject(pInvokeParams[nParam]); + } + else + { + css::uno::Type aDestType(rParam.aType->getTypeClass(), rParam.aType->getName()); + + try + { + pInvokeParams[nParam] = m_xTypeConverter->convertTo(aParams[nParam], aDestType); + } + catch (css::script::CannotConvertException& e) + { + // Add optional parameter index + e.ArgumentIndex = nParam; + throw; + } + } + } + + return aInvokeParams; +} + +css::uno::Any InvocationHelper::invoke(css::script::XInvocation* pInvocation, + const OUString& aFunctionName, + const css::uno::Sequence& aParams, + css::uno::Sequence& aOutParamIndex, + css::uno::Sequence& aOutParam) +{ + // This implementation is heavily inspired by Invocation_Impl::invoke + + ensureIntrospection(pInvocation); + + css::uno::Reference xMethod; + + try + { + xMethod = m_xIntrospectionAccess->getMethod(aFunctionName, ALLOWED_METHODS); + } + catch (css::lang::NoSuchMethodException&) + { + throw css::lang::IllegalArgumentException(u"no such method"_ustr, pInvocation, 0); + } + + css::uno::Sequence aParamInfos = xMethod->getParameterInfos(); + + if (aParams.getLength() != aParamInfos.getLength()) + { + throw css::lang::IllegalArgumentException( + "incorrect number of parameters passed invoking function " + aFunctionName + + ": expected " + OUString::number(aParamInfos.getLength()) + ", got " + + OUString::number(aParams.getLength()), + pInvocation, 1); + } + + ensureTypeConverter(); + + css::uno::Sequence aInvokeParams = prepareInvokeParams(aParams, aParamInfos); + + css::uno::Reference xInvocation(pInvocation); + css::uno::Any aRet = xMethod->invoke(css::uno::Any(xInvocation), aInvokeParams); + + size_t nOutParams = std::count_if( + std::cbegin(aParamInfos), std::cend(aParamInfos), + [](const auto& rParamInfo) { return rParamInfo.aMode != css::reflection::ParamMode_IN; }); + aOutParamIndex.realloc(nOutParams); + aOutParam.realloc(nOutParams); + + sal_Int16* pOutParamIndex = aOutParamIndex.getArray(); + css::uno::Any* pOutParam = aOutParam.getArray(); + + for (sal_Int32 nParam = 0, nOutParam = 0, nParams = aParamInfos.getLength(); nParam < nParams; + ++nParam) + { + if (aParamInfos[nParam].aMode == css::reflection::ParamMode_IN) + continue; + + pOutParamIndex[nOutParam] = nParam; + pOutParam[nOutParam] = aInvokeParams[nParam]; + nOutParam++; + } + + return aRet; +} + +void InvocationHelper::setValue(css::script::XInvocation* pInvocation, + const OUString& aPropertyName, const css::uno::Any& aValue) +{ + ensureIntrospection(pInvocation); + + css::uno::Reference xPropertySet(m_xIntrospectionAccess, + css::uno::UNO_QUERY); + + if (!xPropertySet.is()) + throw css::beans::UnknownPropertyException(aPropertyName); + + css::beans::Property aProp; + + try + { + aProp = m_xIntrospectionAccess->getProperty(aPropertyName, ALLOWED_PROPERTIES); + } + catch (css::container::NoSuchElementException&) + { + throw css::beans::UnknownPropertyException(aPropertyName); + } + + ensureTypeConverter(); + + css::uno::Any aConvertedValue = m_xTypeConverter->convertTo(aValue, aProp.Type); + + try + { + return xPropertySet->setPropertyValue(aPropertyName, aConvertedValue); + } + catch (css::lang::WrappedTargetException& e) + { + throw css::reflection::InvocationTargetException(e.Message, pInvocation, e.TargetException); + } + catch (css::beans::PropertyVetoException& e) + { + throw css::reflection::InvocationTargetException(e.Message, pInvocation, css::uno::Any(e)); + } +} + +css::uno::Any InvocationHelper::getValue(css::script::XInvocation* pInvocation, + const OUString& aPropertyName) +{ + // Make sure the introspection access implements XPropertySet and that the property is one of + // the ones allowed by hasProperty. + + if (!hasProperty(pInvocation, aPropertyName)) + throw css::beans::UnknownPropertyException(aPropertyName); + + css::uno::Reference xPropertySet(m_xIntrospectionAccess, + css::uno::UNO_QUERY_THROW); + + try + { + return xPropertySet->getPropertyValue(aPropertyName); + } + catch (css::lang::WrappedTargetException& e) + { + throw css::reflection::InvocationTargetException(e.Message, pInvocation, e.TargetException); + } +} + +sal_Bool InvocationHelper::hasMethod(css::script::XInvocation* pInvocation, const OUString& aName) +{ + ensureIntrospection(pInvocation); + + return m_xIntrospectionAccess->hasMethod(aName, ALLOWED_METHODS); +} + +sal_Bool InvocationHelper::hasProperty(css::script::XInvocation* pInvocation, const OUString& aName) +{ + ensureIntrospection(pInvocation); + + css::uno::Reference xPropertySet(m_xIntrospectionAccess, + css::uno::UNO_QUERY); + + return xPropertySet.is() && m_xIntrospectionAccess->hasProperty(aName, ALLOWED_PROPERTIES); +} + +void InvocationHelper::ensureTypeConverter() +{ + if (m_xTypeConverter.is()) + return; + + m_xTypeConverter = css::script::Converter::create(m_xContext); +} + +void InvocationHelper::ensureIntrospection(css::script::XInvocation* pInvocation) +{ + if (m_xIntrospectionAccess.is()) + return; + + css::uno::Reference xIntrospection + = css::beans::theIntrospection::get(m_xContext); + + css::uno::Reference xInvocation(pInvocation); + m_xIntrospectionAccess = xIntrospection->inspect(css::uno::Any(xInvocation)); +} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/include/comphelper/invocationhelper.hxx b/include/comphelper/invocationhelper.hxx new file mode 100644 index 000000000000..03e03fce6e2b --- /dev/null +++ b/include/comphelper/invocationhelper.hxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#pragma once + +#include + +#if defined LIBO_INTERNAL_ONLY + +#include + +#include +#include +#include + +namespace com::sun::star::beans +{ +class XIntrospectionAccess; +class XPropertySet; +} + +namespace com::sun::star::reflection +{ +struct ParamInfo; +} + +namespace com::sun::star::script +{ +class XInvocation; +class XTypeConverter; +} + +namespace com::sun::star::uno +{ +class XComponentContext; +} + +namespace comphelper +{ +/** Helper class to implement css::script::Invocation. + + The methods of this implementation use the css::beans::XIntrospection service to query the + methods and properties advertised by the interfaces of the object. The implementation can use + this to provide extra methods and properties. It should have an instance of this class as a + private member and then call the helper methods passing ‘this’ as the first parameter when it + encounters unhandled names in order to continue providing access to the base methods via the + XInvocation interface. +*/ +class COMPHELPER_DLLPUBLIC InvocationHelper +{ +public: + InvocationHelper(const css::uno::Reference& xContext); + + // All the methods of XInvocation. The implementation can call these passing ‘this’ as the first + // parameter. + css::uno::Reference + getIntrospection(css::script::XInvocation* pInvocation); + css::uno::Any invoke(css::script::XInvocation* pInvocation, const OUString& aFunctionName, + const css::uno::Sequence& aParams, + css::uno::Sequence& aOutParamIndex, + css::uno::Sequence& aOutParam); + void setValue(css::script::XInvocation* pInvocation, const OUString& aPropertyName, + const css::uno::Any& aValue); + css::uno::Any getValue(css::script::XInvocation* pInvocation, const OUString& aPropertyName); + sal_Bool hasMethod(css::script::XInvocation* pInvocation, const OUString& aName); + sal_Bool hasProperty(css::script::XInvocation* pInvocation, const OUString& aName); + +private: + css::uno::Reference m_xContext; + css::uno::Reference m_xTypeConverter; + css::uno::Reference m_xIntrospectionAccess; + + void ensureTypeConverter(); + void ensureIntrospection(css::script::XInvocation* pInvocation); + css::uno::Sequence + prepareInvokeParams(const css::uno::Sequence& aParams, + const css::uno::Sequence& aParamInfos); +}; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */