[Bug 32316] Autodesk 3ds Max 9 32-bit exits silently or crashes on startup with Wine-Mono

WineHQ Bugzilla wine-bugs at winehq.org
Sun Dec 13 17:22:18 CST 2020


https://bugs.winehq.org/show_bug.cgi?id=32316

--- Comment #18 from Anastasius Focht <focht at 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/mcs/class/corlib/System.Runtime.InteropServices/Marshal.cs#L509

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/mcs/class/corlib/System.Reflection/RuntimeMethodInfo.cs#L369

--- 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/mcs/class/corlib/System.Reflection/EventInfo.cs#L44

--- 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

-- 
Do not reply to this email, post in Bugzilla using the
above URL to reply.
You are receiving this mail because:
You are watching all bug changes.



More information about the wine-bugs mailing list