IronLanguages / ironpython3

Implementation of Python 3.x for .NET Framework that is built on top of the Dynamic Language Runtime.
Apache License 2.0
2.5k stars 286 forks source link

ipy throws StandardError on EventHandler #1492

Open strongly-typed opened 2 years ago

strongly-typed commented 2 years ago

Concerns IronPython 2.7.12 2.7.12.0 on .NET 4.0.30319.42000 and IronPython 3.4.0b1. Maybe related to IronLanguages/ironpython2#594

Description

Assigning an EventHandler throws a StandardError in ipy with external dll. It works in the C# project but fails with ipy. dll is an external dll from ABB's PC SDK . It works with an older version of the dll.

Steps to Reproduce

Works in C# (full code at end of issue)

ctrl.EventLog.MessageWritten += new EventHandler<MessageWrittenEventArgs>(ctrl_EventMessageWritten);

In ipy:

delegate = EventHandler[EventLogDomain.MessageWrittenEventArgs](message)
c.EventLog.MessageWritten += delegate

I get a StandardError: Exception has been thrown by the target of an invocation. with a newer dll from ABB.

All dll versions work in C#. Works in ipy only with ABB dll version up to (and including) 6.8.8307.1040 . Starting with the next version PC SDK 2019.3 aka 7.8.8617.559 the error is thrown.

How can I debug what the underlying problem is? I tried -v -u -X:Debug -X:ExceptionDetail -X:FullFrames -X:ShowClrExceptions -X:Tracing -X:PassExceptions but did not get any deeper insights. Is this more related to ipy or to ABB? (All their dlls work in C#)

Full code

Full Python code:

import clr
import os
from encodings import utf_8  # Preload so that module is not loaded when exeception occurrs

from System import EventHandler

clr.AddReferenceToFileAndPath(os.path.abspath("ABB.Robotics.Controllers.PC.dll"))

def message(sender, event_args):
    print('message')
    return None

from ABB.Robotics.Controllers import *

try:
    netScanner = Discovery.NetworkScanner()
except SystemError as se:
    print('Cannot create NetworkScanner. Are RobotStudio.Services.RobApi.Desktop.dll and RobotStudio.Services.RobApi.dll present?')

controllers = netScanner.GetControllers()

for cinfo in controllers:
    print("cinfo", cinfo.Id)

if len(controllers) > 0:
    cinfo = controllers[0]
    # c = Controller.Connect(cinfo, ConnectionType.Standalone)  # New variant, replaced CreateFrom
    c = ControllerFactory.CreateFrom(cinfo)
    c.Logon(UserInfo.DefaultUser)

    delegate = EventHandler[EventLogDomain.MessageWrittenEventArgs](message)

    try:
        c.EventLog.MessageWritten += delegate
        # c.EventLog.MessageWritten += message  # Alternative
    except Exception as ex:
        import traceback
        traceback.print_exc()

Full code in C#, example project from ABB. SDK must be installed and ABB.Robotics.Controllers.PC.dll, RobotStudio.Services.RobApi.Desktop.dll and RobotStudio.Services.RobApi.dll must be referenced.

/* 

Copyright (c) 2011, ABB
All rights reserved.

Redistribution and use in source and binary forms, with
or without modification, are permitted provided that 
the following conditions are met:

  * Redistributions of source code must retain the 
    above copyright notice, this list of conditions 
    and the following disclaimer.
  * Redistributions in binary form must reproduce the 
    above copyright notice, this list of conditions 
    and the following disclaimer in the documentation 
    and/or other materials provided with the 
    distribution.
  * Neither the name of ABB nor the names of its 
    contributors may be used to endorse or promote 
    products derived from this software without 
    specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

using ABB.Robotics.Controllers;
using ABB.Robotics.Controllers.Discovery;
using ABB.Robotics.Controllers.EventLogDomain;

using System;

namespace ControllerAPI
{
    /// <summary>
    /// Listens for events on the Controller object
    /// </summary>
    class Listener
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [MTAThread]
        static void Main(string[] args)
        {
            Controller ctrl;
            while( ( ctrl = CreateController() ) == null )
            {
            }

            ctrl.Logon( UserInfo.DefaultUser );
            ctrl.OperatingModeChanged += new EventHandler<OperatingModeChangeEventArgs>(ctrl_OperatingModeChanged);
            ctrl.EventLog.MessageWritten += new EventHandler<MessageWrittenEventArgs>(ctrl_EventMessageWritten);

            Console.WriteLine("Press any key to terminate");
            Console.ReadKey();

            ctrl.Logoff();
        }

        static Controller CreateController()
        {
            NetworkScanner scanner = new NetworkScanner();
            ControllerInfo[] controllers = scanner.GetControllers( NetworkScannerSearchCriterias.Real);
            if( controllers.Length > 0 )
            {
                Controller dynamic = ControllerFactory.CreateFrom( controllers[0] );
                return dynamic;
            }
            return null;
        }

        private static void ctrl_OperatingModeChanged(object sender, OperatingModeChangeEventArgs e)
        {
            Console.WriteLine( "New Operating mode at: {0} new mode is: {1}", e.Time, e.NewMode );          
        }

        private static void ctrl_EventMessageWritten(object sender, MessageWrittenEventArgs e)
        {
            Console.WriteLine("New Event at: {0} event is {1}", e.Time, e.Message.Title);
        }
    }
}
strongly-typed commented 2 years ago

Added print(ex.clsException) and got

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentException: Incorrect number of arguments supplied for call to method 'Void _Scripting_(System.Object[], System.Object, ABB.Robotics.Controllers.EventLogDomain.MessageWrittenEventArgs)'
   at System.Linq.Expressions.Expression.ValidateArgumentCount(MethodBase method, ExpressionType nodeKind, Int32 count, ParameterInfo[] pis)
   at System.Linq.Expressions.Expression.Call(MethodInfo method, Expression arg0, Expression arg1)
   at ControllerAPI.Internal.WeakEventSource`1.WeakDelegate.CreateOpenHandler(MethodInfo method)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at ControllerAPI.Internal.WeakEventSource`1.WeakDelegate..ctor(Object sender, Delegate handler, Boolean strong)
   at ControllerAPI.Internal.WeakEventSource`1.<>c__DisplayClass3_0.<Subscribe>b__0(Delegate d)
   at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at ControllerAPI.Internal.WeakEventSource`1.Subscribe(Object sender, EventHandler`1 handler, Boolean strong)
   at ABB.Robotics.Controllers.EventLogDomain.EventLog.Subscribe(EventHandler`1 handler)
   --- End of inner exception stack trace ---
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Scripting.Actions.EventTracker.AddHandler(Object target, Object handler, DynamicDelegateCreator delegateCreator)
   at IronPython.Runtime.Types.ReflectedEvent.BoundEvent.InPlaceAdd(CodeContext context, Object func)
   at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
   at Microsoft.Scripting.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
   at Microsoft.Scripting.Interpreter.Interpreter.Run(InterpretedFrame frame)
slozier commented 2 years ago

Thanks for the report. I managed to reproduce the issue here. I think it's ABB doing something funny with the EventHandler delegates (but I don't know enough about all this delegate stuff to rule out IronPython being at fault). You can maybe try wrapping your delegate like this:

delegate = EventHandler[EventLogDomain.MessageWrittenEventArgs](message)
delegate = EventHandler[EventLogDomain.MessageWrittenEventArgs](delegate.Invoke)
strongly-typed commented 2 years ago

Thank you @slozier for looking into this and suggesting a solution / workaround.

I can confirm that this works with the latest ABB SDK (2022.1) both with IPython 2.7.12 and 3.4 beta.

Is this more an artifact of IronPython or is it worth to involve ABB in this matter?