https://bugs.winehq.org/show_bug.cgi?id=32316
--- Comment #18 from Anastasius Focht focht@gmx.net --- Hello folks,
--- snip --- [00000138:] EXCEPTION handling: System.NotSupportedException: Event BackgroundImageLayoutChanged is not valid on this ActiveX control. EXCEPTION: catch found at clause 0 of <Module>:MXS_dotNet.DotNetObjectWrapper.CreateEventHandlerDelegates (MXS_dotNet.DotNetObjectWrapper* modopt(System.Runtime.CompilerServices.IsConst) modopt(System.Runtime.CompilerServices.IsConst),object,System.Reflection.MethodInfo) --- snip ---
Turns out the 'System.NotSupportedException' messages of Mono debug trace ought to be harmless, the exception is expected and *should* be handled in the end.
The relevant piece of code in mixed mode C++ assembly:
--- snip --- internal static unsafe void MXS_dotNet::DotNetObjectWrapper::CreateEventHandlerDelegates([In] DotNetObjectWrapper* obj0, object targetWrapper, MethodInfo mi) { ... try { DotNetObjectWrapper* netObjectWrapperPtr1 = obj0; object target = __calli((__FnPtr<object (IntPtr)>) *(int*) *(int*) netObjectWrapperPtr1)((IntPtr) netObjectWrapperPtr1); DotNetObjectWrapper* netObjectWrapperPtr2 = obj0; System.Type type1 = __calli((__FnPtr<System.Type (IntPtr)>) *(int*) (*(int*) netObjectWrapperPtr2 + 4))((IntPtr) netObjectWrapperPtr2); if (targetWrapper == null || (object) mi == null || (object) type1 == null) return;
System.Type type2 = targetWrapper.GetType(); System.Type returnType1 = mi.ReturnType; BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy; if (target != null) bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy; foreach (EventInfo eventInfo in type1.GetEvents(bindingAttr)) { System.Type eventHandlerType = eventInfo.EventHandlerType; System.Type returnType2 = eventHandlerType.GetMethod("Invoke").ReturnType; ParameterInfo[] parameters = eventHandlerType.GetMethod("Invoke").GetParameters(); System.Type[] parameterTypes = new System.Type[parameters.Length + 1]; parameterTypes[0] = type2; int index1; for (int index2 = 0; index2 < parameters.Length; index2 = index1) { index1 = index2 + 1; parameterTypes[index1] = parameters[index2].ParameterType; } DynamicMethod dynamicMethod = new DynamicMethod(eventInfo.Name, returnType2, parameterTypes, type2); ILGenerator ilGenerator = dynamicMethod.GetILGenerator(); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldstr, eventInfo.Name); LocalBuilder local = ilGenerator.DeclareLocal(typeof (object[])); ilGenerator.Emit(OpCodes.Ldc_I4, parameters.Length); ilGenerator.Emit(OpCodes.Newarr, typeof (object)); ilGenerator.Emit(OpCodes.Stloc, local); int num2; for (int index2 = 0; index2 < parameters.Length; index2 = num2) { ilGenerator.Emit(OpCodes.Ldloc, local); ilGenerator.Emit(OpCodes.Ldc_I4, index2); num2 = index2 + 1; ilGenerator.Emit(OpCodes.Ldarg, num2); ilGenerator.Emit(OpCodes.Stelem_Ref); } ilGenerator.Emit(OpCodes.Ldloc, local); ilGenerator.Emit(OpCodes.Callvirt, mi); if ((object) returnType1 == (object) typeof (void)) { if ((object) returnType2 != (object) typeof (void)) ilGenerator.Emit(OpCodes.Ldnull); } else if ((object) returnType2 == (object) typeof (void)) ilGenerator.Emit(OpCodes.Pop); else ilGenerator.Emit(OpCodes.Castclass, returnType2); ilGenerator.Emit(OpCodes.Ret); Delegate @delegate = dynamicMethod.CreateDelegate(eventHandlerType, targetWrapper); try { eventInfo.AddEventHandler(target, @delegate); __calli((__FnPtr<void (IntPtr, event_delegate_pair)>) *(int*) (*(int*) obj0 + 12))((event_delegate_pair) (IntPtr) obj0, (IntPtr) new event_delegate_pair(eventInfo.Name, @delegate)); } catch (Exception ex) { if ((object) ex.InnerException.GetType() != (object) typeof (NotSupportedException)) throw; } } } --- snip ---
The inner try() / catch() block around 'AddEventHandler' is supposed to swallow all exceptions of type 'NotSupportedException' and to rethrow any other.
Looking further we find:
--- snip --- ... [00000138: 1.32092 7] ENTER:c System.Exception:get_InnerException ()(this:0D015828[System.NotSupportedException 3dsmax.exe]) [00000138: 1.32094 7] LEAVE:c System.Exception:get_InnerException ()([OBJECT:00000000] 0138:trace:seh:dispatch_exception code=c0000005 flags=0 addr=0FFE246F ip=0ffe246f tid=0138 0138:trace:seh:dispatch_exception info[0]=00000000 0138:trace:seh:dispatch_exception info[1]=00000000 0138:trace:seh:dispatch_exception eax=00000000 ebx=00000010 ecx=0031de70 edx=00000000 esi=0e41c900 edi=0000005c 0138:trace:seh:dispatch_exception ebp=0031e168 esp=0031df60 cs=0023 ds=002b es=002b fs=0063 gs=006b flags=00010202 0138:trace:seh:call_vectored_handlers calling handler at 0CA303D0 code=c0000005 flags=0 0138:trace:seh:call_vectored_handlers handler at 0CA303D0 returned ffffffff [00000138: 1.32121 7] ENTER:c (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)([System.NullReferenceException:0D015928], 00000000, 0031DBE0, 104C7D58) [00000138: 1.32123 8] ENTER:c System.NullReferenceException:.ctor ()(this:0D015928[System.NullReferenceException 3dsmax.exe]) [00000138: 1.32124 9] ENTER:c System.SystemException:.ctor (string)(this:0D015928[System.NullReferenceException 3dsmax.exe], [STRING:0E3DC320:Object reference not set to an instance of an object.]) ... [00000138:] EXCEPTION handling: System.NullReferenceException: Object reference not set to an instance of an object [00000138: 1.32148 7] ENTER:c System.Runtime.InteropServices.Marshal:GetExceptionPointers ()() [00000138: 1.32154 8] ENTER:c (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)([System.NotImplementedException:0D015AA0], 00000000, 0031DB58, 104C84C8) [00000138: 1.32156 9] ENTER:c System.NotImplementedException:.ctor ()(this:0D015AA0[System.NotImplementedException 3dsmax.exe]) [00000138: 1.32157 10] ENTER:c System.SystemException:.ctor (string)(this:0D015AA0[System.NotImplementedException 3dsmax.exe], [STRING:0E3DC3A0:The method or operation is not implemented.]) --- snip ---
ex.InnerException.GetType() causes another exception 'System.NullReferenceException' because the inner exception object is null.
That one is caught by the outer try() / catch() block:
--- snip --- try { ... } catch (Exception ex1) when (Module.__CxxExceptionFilter((void*) Marshal.GetExceptionPointers(), (void*) &Module.FAVMAXScriptException, 8, (void*) 0) != 0) { uint num2 = 0; Module.__CxxRegisterExceptionObject((void*) Marshal.GetExceptionPointers(), (void*) num1); try { try { Module._CxxThrowException((void*) 0, (_s__ThrowInfo*) 0); return; } --- snip ---
Well, not really. Wine-Mono doesn't implement 'System.Runtime.InteropServices.Marshal.GetExceptionPointers' causing a another exception seen as 'System.NotImplementedException', leaking to the outside.
https://github.com/mono/mono/blob/85056b086f950799b79a8f999e9a3edd406b0785/m...
Related discussion in:
https://github.com/dotnet/standard/issues/870
But that's a follow-up/secondary problem which becomes a non-issue when the root cause is fixed.
---
The explanation why the sequence works with native .NET Framework is that there has to be another try() / catch() clause in between which wraps 'NotSupportedException' in another exception object to become the inner exception. Wine-Mono doesn't do this.
System.Reflection.EventInfo -> AddEventHandler System.Reflection.(Runtime)MethodInfo -> Invoke
https://github.com/mono/mono/blob/40372422e7bfa506e6a93fd06e9315513d9341c6/m...
--- snip --- public override Object Invoke (Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) { if (!IsStatic) { if (!DeclaringType.IsInstanceOfType (obj)) { if (obj == null) throw new TargetException ("Non-static method requires a target."); else throw new TargetException ("Object does not match target type."); } }
if (binder == null) binder = Type.DefaultBinder;
/*Avoid allocating an array every time*/ ParameterInfo[] pinfo = GetParametersInternal (); ConvertValues (binder, parameters, pinfo, culture, invokeAttr);
if (ContainsGenericParameters) throw new InvalidOperationException ("Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true.");
Exception exc; object o = null;
if ((invokeAttr & BindingFlags.DoNotWrapExceptions) == 0) { try { o = InternalInvoke (obj, parameters, out exc); } catch (ThreadAbortException) { throw; #if MOBILE } catch (MethodAccessException) { throw; #endif #if NETCORE } catch (Mono.NullByRefReturnException) { throw new NullReferenceException (); #endif } catch (OverflowException) { throw; } catch (Exception e) { throw new TargetInvocationException (e); } } else { #if NETCORE try { o = InternalInvoke (obj, parameters, out exc); } catch (Mono.NullByRefReturnException) { throw new NullReferenceException (); } #else o = InternalInvoke (obj, parameters, out exc); #endif }
if (exc != null) throw exc; return o; } --- snip ---
'InternalInvoke' is wrapped in try() / catch () block which wraps any non-critical exception in a 'TargetInvocationException'. It calls it with 'TargetInvocationException(System.Exception inner)' ctor which is exactly what is needed here.
Why isn't 'Invoke' getting called?
https://github.com/mono/mono/blob/f89fdf28a327e8c99e0110f47a1cc17c5bc5eebb/m...
--- snip --- public virtual void AddEventHandler (object target, Delegate handler) { // this optimization cause problems with full AOT // see bug https://bugzilla.xamarin.com/show_bug.cgi?id=3682 #if FULL_AOT_RUNTIME MethodInfo add = GetAddMethod (); if (add == null) throw new InvalidOperationException (SR.InvalidOperation_NoPublicAddMethod); if (target == null && !add.IsStatic) throw new TargetException ("Cannot add a handler to a non static event with a null target"); add.Invoke (target, new object [] {handler}); #else if (cached_add_event == null) { MethodInfo add = GetAddMethod (); if (add == null) throw new InvalidOperationException (SR.InvalidOperation_NoPublicAddMethod);
if (add.DeclaringType.IsValueType) { if (target == null && !add.IsStatic) throw new TargetException ("Cannot add a handler to a non static event with a null target"); add.Invoke (target, new object [] {handler}); return; } cached_add_event = CreateAddEventDelegate (add); } //if (target == null && is_instance) // throw new TargetException ("Cannot add a handler to a non static event with a null target"); cached_add_event (target, handler); #endif } --- snip ---
We are in the "else" part (not FULL_AOT_RUNTIME), and 'CreateAddEventDelegate' gets called which doesn't do any further exception wrapping on its own.
--- quote --- The idea behing this optimization is to use a pair of delegates to simulate the same effect of doing a reflection call. The first delegate performs casting and boxing, the second dispatch to the add_... method. --- quote ---
That's about it.
Regards