Python: the second and third arguments of invoke()?

Keyboard macros or custom scripts

Python: the second and third arguments of invoke()?

Postby HippoMan » Wed Dec 23, 2015 1:05 am

I'm calling an external python function as follows. I know how to pass arguments to my python function via the first parameter passed to the invoke() call (in this case, the values "a", "b", and "c"), and I know that these become the arguments that are passed to the python function.

However, I have no idea how the second and third arguments to "invoke" are used when calling python (variables "secondArg" and "thirdArg" in my example). Is there a way to access these second and third arguments via the python function or perhaps for this python function to fill those arguments with return values?

Thank you in advance.

Code: Select all   Expand viewCollapse view
Global g_MasterScriptProvider
Const URL_Main = "vnd.sun.star.script:"
Const URL_Args = "?language=Python&location=user"

Function getMasterScriptProvider()
    if not isObject(g_MasterScriptProvider) then
       s = "com.sun.star.script.provider.MasterScriptProviderFactory"
       g_MasterScriptProvider = createUnoService(s).createScriptProvider("")
   end if
   getMasterScriptProvider = g_MasterScriptProvider
End Function

Function PythonFunction()
    scriptName = "pyfunc"
    fullURI = URL_Main & scriptName & ".py$" & scriptName & URL_Args
    m = getMasterScriptProvider()
    s = m.GetScript(fullURI)
    secondArg = Array()
    thirdArg = Array()
    result = s.invoke(Array("a", "b", "c"), secondArg, thirdArg)
    PythonFunction = result
End Function
--
LibreOfficeDev 5.0.4.2 on Ubuntu
Take a hippopotamus to lunch today.
HippoMan
 
Posts: 15
Joined: Wed Dec 23, 2015 12:51 am

Re: Python: the second and third arguments of invoke()?

Postby B Marcelly » Wed Dec 23, 2015 9:49 am

Read once again the API documentation:
https://www.openoffice.org/api/docs/common/ref/com/sun/star/script/provider/XScript.html#invoke
The parameters of the second and third arguments are filled by execution of the instruction. You have to read them after execution. The example is as clear as possible for such strange interface.

Instead of this complex way of transmitting results, use the return value. It may be any value, e.g. an array of data.
Bernard

OpenOffice.org 1.1.5 / Apache OpenOffice 4.1.1 / LibreOffice 5.0.5
MS-Windows 7 Home SP1
B Marcelly
Volunteer
 
Posts: 1160
Joined: Mon Oct 08, 2007 1:26 am
Location: France, Paris area

Re: Python: the second and third arguments of invoke()?

Postby HippoMan » Wed Dec 23, 2015 11:29 am

I have already read that documentation. However, the second and third arguments are always empty after execution of the invoke() method, no matter how I call my python function, and no matter what my python function does.

How does the python function, itself, control what goes into those last two arguments? It seems that those two arguments are totally ignored when the external function is written in python. Is that correct? If not, then what can be done within the python function, itself, to cause values to be placed within those last two arguments?

Does anyone have any sample python code which, when called via invoke(), will actually cause values to be placed within these last two arguments?

Here are three examples which seem to show that python has no way of causing any values to appear in the second and third arguments passed to invoke(). Each of them are invoked via LibreOffice Basic using the code I posted above.

For my first example, assume that my python function is written as follows. When called via the Basic code I listed above, args[0] of the python function will contain the value "a", args[1] will contain "b", and args[2] will contain "c", and the value "foobar: a b c" is returned to the LibreOffice Basic calling program. However, "secondArg" and "thirdArg" always come back empty, with each of their ubound() values set to -1. How should I rewrite my python function to cause values to be placed in "secondArg" and "thirdArg" of my example above?

Code: Select all   Expand viewCollapse view
def pyfunc(*args):
  result = 'foobar: ' + ' '.join(args)
  return result


Secondly, if I write the python function as follows, a two-element array gets returned to my LibreOffice Basic calling program, but "secondArg" and "thirdArg" still come back empty. The returned two-element array looks like this:

[ 3, "foobar: a b c" ]

Code: Select all   Expand viewCollapse view
def pyfunc(*args):
  result = 'foobar: ' + ' '.join(args)
  return ( len(args), result )


And finally, if I write the function as follows, I get the same results, again with empty "secondArg" and "thirdArg" values coming back after the call.

Code: Select all   Expand viewCollapse view
def pyfunc(a0, a1, a2):
  result = 'foobar: ' + str(a0) + ' ' + str(a1) + ' ' + str(a2)
  # Python passes arguments by value, so these settings of the three function arguments have
  # no effect when the function returns.
  a0 = 1000
  a1 = ( 1, 2 )
  a2 = ( 4000, 5000, 6000, 7000 )
  return ( 3, result )


Thank you.
--
LibreOfficeDev 5.0.4.2 on Ubuntu
Take a hippopotamus to lunch today.
HippoMan
 
Posts: 15
Joined: Wed Dec 23, 2015 12:51 am

Re: Python: the second and third arguments of invoke()?

Postby HippoMan » Wed Dec 23, 2015 3:28 pm

Follow-up ...

I found the following in the a00-4.1.2 source code. It's part of the Adaper::invoke() method in aoo-4.1.2/main/pyuno/source/module/pyuno_adapter.cxx.

Code: Select all   Expand viewCollapse view
        // ... etc.
        if( pyRet.is() )
        {
            ret = runtime.pyObject2Any( pyRet );

            if( ret.hasValue() &&
                ret.getValueTypeClass() == com::sun::star::uno::TypeClass_SEQUENCE &&
                0 != aFunctionName.compareToAscii( "getTypes" ) &&  // needed by introspection itself !
                0 != aFunctionName.compareToAscii( "getImplementationId" ) ) // needed by introspection itself !
            {
                // the sequence can either be
                // 1)  a simple sequence return value
                // 2)  a sequence, where the first element is the return value
                //     and the following elements are interpreted as the outparameter
                // I can only decide for one solution by checking the method signature,
                // so I need the reflection of the adapter !
                aOutParamIndex = getOutIndexes( aFunctionName );
                if( aOutParamIndex.getLength() )
                {
                    // out parameters exist, extract the sequence
                    Sequence< Any  > seq;
                    if( ! ( ret >>= seq ) )
                    {
                        throw RuntimeException(
                            (OUString(
                                RTL_CONSTASCII_USTRINGPARAM(
                                    "pyuno bridge: Couldn't extract out"
                                    " parameters for method "))
                             + aFunctionName),
                            Reference< XInterface > () );
                    }

                    if( aOutParamIndex.getLength() +1 != seq.getLength() )
                    {
                        OUStringBuffer buf;
                        buf.appendAscii( "pyuno bridge: expected for method " );
                        buf.append( aFunctionName );
                        buf.appendAscii( " one return value and " );
                        buf.append( (sal_Int32) aOutParamIndex.getLength() );
                        buf.appendAscii( " out parameters, got a sequence of " );
                        buf.append( seq.getLength() );
                        buf.appendAscii( " elements as return value." );
                        throw RuntimeException(buf.makeStringAndClear(), *this );
                    }

                    aOutParam.realloc( aOutParamIndex.getLength() );
                    ret = seq[0];
                    for( i = 0 ; i < aOutParamIndex.getLength() ; i ++ )
                    {
                        aOutParam[i] = seq[1+i];
                    }
                }
                // else { sequence is a return value !}
            }
        }

        // ... etc. ...

Sequence< sal_Int16 > Adapter::getOutIndexes( const OUString & functionName )
{
    Sequence< sal_Int16 > ret;
    MethodOutIndexMap::const_iterator ii = m_methodOutIndexMap.find( functionName );
    if( ii == m_methodOutIndexMap.end() )
    {

        Runtime runtime;
        {
            PyThreadDetach antiguard;

            // retrieve the adapter object again. It will be the same instance as before,
            // (the adapter factory keeps a weak map inside, which I couldn't have outside)
            Reference< XInterface > unoAdapterObject =
                runtime.getImpl()->cargo->xAdapterFactory->createAdapter( this, mTypes );

            // uuuh, that's really expensive. The alternative would have been, to store
            // an instance of the introspection at (this), but this results in a cyclic
            // reference, which is never broken (as it is up to OOo1.1.0).
            Reference< XIntrospectionAccess > introspection =
                runtime.getImpl()->cargo->xIntrospection->inspect( makeAny( unoAdapterObject ) );

            if( !introspection.is() )
            {
                throw RuntimeException(
                    OUString( RTL_CONSTASCII_USTRINGPARAM( "pyuno bridge: Couldn't inspect uno adapter ( the python class must implement com.sun.star.lang.XTypeProvider !)" ) ),
                    Reference< XInterface > () );
            }

            Reference< XIdlMethod > method = introspection->getMethod(
                functionName, com::sun::star::beans::MethodConcept::ALL );
            if( ! method.is( ) )
            {
                throw RuntimeException(
                    (OUString(
                        RTL_CONSTASCII_USTRINGPARAM(
                            "pyuno bridge: Couldn't get reflection for method "))
                     + functionName),
                    Reference< XInterface > () );
            }

            Sequence< ParamInfo > seqInfo = method->getParameterInfos();
            int i;
            int nOuts = 0;
            for( i = 0 ; i < seqInfo.getLength() ; i ++ )
            {
                if( seqInfo[i].aMode == com::sun::star::reflection::ParamMode_OUT ||
                    seqInfo[i].aMode == com::sun::star::reflection::ParamMode_INOUT )
                {
                    // sequence must be interpreted as return value/outparameter tuple !
                    nOuts ++;
                }
            }

            if( nOuts )
            {
                ret.realloc( nOuts );
                sal_Int32 nOutsAssigned = 0;
                for( i = 0 ; i < seqInfo.getLength() ; i ++ )
                {
                    if( seqInfo[i].aMode == com::sun::star::reflection::ParamMode_OUT ||
                        seqInfo[i].aMode == com::sun::star::reflection::ParamMode_INOUT )
                    {
                        ret[nOutsAssigned] = (sal_Int16) i;
                        nOutsAssigned ++;
                    }
                }
            }
        }
        // guard active again !
        m_methodOutIndexMap[ functionName ] = ret;
    }
    else
    {
        ret = ii->second;
    }
    return ret;
}

// ... etc. ...

Note that the second and third arguments of the invoke() call seem to be filled under the following case: the python function returns a sequence whose second and third elements are sequences, themselves. However, these sequences need to somehow be declared as the following types: com::sun::star::uno::TypeClass_SEQUENCE, and the 2nd and 3rd arguments to invoke() need to be tagged with mode com::sun::star::reflection::ParamMode_OUT or mode com::sun::star::reflection::ParamMode_INOUT. How do I make such declarations within python or within the LibreOffice Basic calling function?

If I simply return python tuples, as in the example below, the second and third arguments of invoke() still end up coming back empty, with their ubound() values each set to -1.

Code: Select all   Expand viewCollapse view
def pyfunc(*args):
   result = 'foobar {0}: [{1}]'.format(len(args), ' '.join(args))
   return ( result, ( 1, 2, 3 ), ( 4, 5, 6 ) )

Last edited by HippoMan on Wed Dec 23, 2015 3:55 pm, edited 1 time in total.
--
LibreOfficeDev 5.0.4.2 on Ubuntu
Take a hippopotamus to lunch today.
HippoMan
 
Posts: 15
Joined: Wed Dec 23, 2015 12:51 am

Re: Python: the second and third arguments of invoke()?

Postby B Marcelly » Wed Dec 23, 2015 3:38 pm

I am no Python specialist but...
Python functions do not offer in/out or out parameters. You can only return something (by return instruction), but you cannot directly change the value of an argument. In other words, parameters are passed by value.
(Except with some tricks, see this FAQ : How do I write a function with output parameters (call by reference)?)

By contrast, Basic for example passes parameters by address, so you can change the value of a parameter, in a Function or in a Sub. This is totally independent of the return value of a function.
Code: Select all   Expand viewCollapse view
Sub Main
Dim bb As Long, beta As String
bb = 987
beta = "Hello"

beta = alpha(bb) ' call of function
MsgBox beta
MsgBox bb
End Sub

Function alpha(aa As Long) As String
aa = 123
alpha = "Finished"
End Function
Bernard

OpenOffice.org 1.1.5 / Apache OpenOffice 4.1.1 / LibreOffice 5.0.5
MS-Windows 7 Home SP1
B Marcelly
Volunteer
 
Posts: 1160
Joined: Mon Oct 08, 2007 1:26 am
Location: France, Paris area

Re: Python: the second and third arguments of invoke()?

Postby HippoMan » Wed Dec 23, 2015 4:05 pm

Thank you very much. Yes, I already understand how this works in Basic, and I already had put a comment in my sample python code describing how arguments are only passed by value in that language.

The entire purpose of this thread is for me to determine if the second and third arguments to invoke() are meaningful at all when running python functions, and if so, how to access that info. The source code from pyuno_adapter.cxx that I posted seems to indicate that the second and third invoke() arguments are indeed accessible, but I don't know how to code this in python.
--
LibreOfficeDev 5.0.4.2 on Ubuntu
Take a hippopotamus to lunch today.
HippoMan
 
Posts: 15
Joined: Wed Dec 23, 2015 12:51 am

Answer: 2nd and 3rd args of invoke() ignored in python

Postby HippoMan » Thu Dec 24, 2015 8:56 pm

Well, after much digging, I finally found the answer to my question: it's indeed impossible in python functions to see, modify, or access in any way the second and third arguments of invoke(). The documentation here is therefore incorrect with regard to scripts that are written in python.

In the "program" subdirectory of my libreoffice installation, there is a file called pythonscript.py. It's the module which, among other things, is used to actually invoke supplied python scripts. In the snippet of code I am enclosing here from pythonscript.py, you can see that the "out" and "outindex" parameters that come in from invoke() are totally ignored. The only argument which is passed on to the specified python function is the first parameter, named "args" in this code, and nothing from within the "out" or "outindex" parameters gets passed back to the caller.

I'm sure it's possible to hack pythonscript.py to make it do something meaningful with the second and third arguments that get passed in to invoke(). However, out of the box, these parameters are ignored. (See follow-up, below)

Here's the code:

Code: Select all   Expand viewCollapse view
class PythonScript( unohelper.Base, XScript ):
    def __init__( self, func, mod ):
        self.func = func
        self.mod = mod
    def invoke(self, args, out, outindex):
        log.debug( "PythonScript.invoke " + str( args ) )
        try:
            ret = self.func( *args )
        except UnoException as e:
            # UNO Exception continue to fly ...
            text = lastException2String()
            complete = "Error during invoking function " + \
                str(self.func.__name__) + " in module " + \
                self.mod.__file__ + " (" + text + ")"
            log.debug( complete )
            # some people may beat me up for modifying the exception text,
            # but otherwise office just shows
            # the type name and message text with no more information,
            # this is really bad for most users.
            e.Message = e.Message + " (" + complete + ")"
            raise
        except Exception as e:
            # General python exception are converted to uno RuntimeException
            text = lastException2String()
            complete = "Error during invoking function " + \
                str(self.func.__name__) + " in module " + \
                self.mod.__file__ + " (" + text + ")"
            log.debug( complete )
            raise RuntimeException( complete , self )
        log.debug( "PythonScript.invoke ret = " + str( ret ) )
        return ret, (), ()


Follow-up: I did some debugging of this invoke() method, and it turns out that the "out" and "outindex" parameters both come to this method set to None, no matter what values they are given in the calling routine. They are not even set to empty lists nor empty tuples. This confirms that the second and third parameters to invoke() are meaningless when writing python functions.
--
LibreOfficeDev 5.0.4.2 on Ubuntu
Take a hippopotamus to lunch today.
HippoMan
 
Posts: 15
Joined: Wed Dec 23, 2015 12:51 am


Return to OpenOffice Basic, Python, BeanShell, JavaScript

Who is online

Users browsing this forum: No registered users and 2 guests