views:

81

answers:

4

Hello. We have encountered some strange things while calling reflected generic delegates. In some cases with attatched debuger we can make impossible call, while without debugger we cannot catch any exception and application fastfails.

Here is the code:

using System;
using System.Windows.Forms;
using System.Reflection;

namespace GenericDelegate
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private delegate Class2 Delegate1();

        private void button1_Click(object sender, EventArgs e)
        {
            MethodInfo mi = typeof (Class1<>).GetMethod("GetClass", BindingFlags.NonPublic | BindingFlags.Static);
            if (mi != null)
            {
                Delegate1 del = (Delegate1) Delegate.CreateDelegate(typeof (Delegate1), mi);
                MessageBox.Show("1");
                try
                {
                    del();
                }
                catch (Exception)
                {
                    MessageBox.Show("No, I can`t catch it");
                }
                MessageBox.Show("2");
                mi.Invoke(null, new object[] {});//It's Ok, we'll get exception here
                MessageBox.Show("3");
            }
        }


        class Class2
        {

        }


        class Class1<T> : Class2
        {
            internal static Class2 GetClass()
            {
                Type type = typeof(T);
                MessageBox.Show("Type name " + type.FullName +" Type: " + type + " Assembly " + type.Assembly);
                return new Class1<T>();
            }
        }
    }
}

There are two problems:

  1. Behavior differs with debugger and without
  2. You cannot catch this error without debugger by clr tricks. It's just not the clr exception. There are memory acces vialation, reading zero pointer inside of internal code.

Use case: You develop something like plugins system for your app. You read external assembly, find suitable method in some type, and execute it. And we just forgot about that we need to check up is the type generic or not. Under VS (and .net from 2.0 to 4.0) everything works fine. Called function does not uses static context of generic type and type parameters. But without VS application fails with no sound. We even cannot identify call stack attaching debuger.

Tested with .net 4.0

The question is why VS catches but runtime do not?

+1  A: 

I'm not sure I follow exactly what you want to do, but if you put your code in a try/catch block there certainly is an exception thrown.

Radoslav Hristov
There are some information that in some cases calling del(); causes exception like "An object with a incomplete object graph."But on my machines it does not.
Andrew_B
Unfortunately I can't try it on 4.0 but on VS2005/2.0, if you put a try/catch around the call to Application.Run statement, it catches the exception. Still, this doesn't really resolve your issue.
Radoslav Hristov
If debugger of VS is attatched to process function "del();" is executed, while it should not. It should fail, because no type parameter is specified.If the debugger is not attached it kills process without clr exception.
Andrew_B
Well, I tried just what I wrote above - a try/catch at the top level, before the form is created. It certainly catches an exception of type System.AccessViolationException.
Radoslav Hristov
With debugger or without?
Andrew_B
Without. With Debugger attached I get a different exception: InvalidOperationException.
Radoslav Hristov
+1  A: 

Just tested under VS 2008 VS does not catch exception at all.
Here is modified test case

static class Program
    {
        private delegate Foo Delegate1();
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            MethodInfo mi = typeof(Bar<>).GetMethod("GetClass", BindingFlags.NonPublic | BindingFlags.Static);
            if (mi != null)
            {
                var del = (Delegate1)Delegate.CreateDelegate(typeof(Delegate1), mi);

                MessageBox.Show("1");
                try
                {
                    del();
                    //mi.Invoke(null, BindingFlags.NonPublic | BindingFlags.Static, null, null,CultureInfo.InvariantCulture);
                }
                catch (Exception)
                {
                    MessageBox.Show("No, I can`t catch it");
                }
                MessageBox.Show("2");
                mi.Invoke(null, new object[] { });//It's Ok, we'll get exception here
                MessageBox.Show("3");
            }
        }
        class Foo
        {

        }
        class Bar<T> : Foo
        {
            internal static Foo GetClass()
            {
                Type type = typeof(T);
                MessageBox.Show("Type name " + type.FullName + " Type: " + type + " Assembly " + type.Assembly);
                return new Bar<T>();
            }
        }
    }

but if you comment del() und uncomment mi.Invoke() you will get nice exception
true.

Late bound operations cannot be performed on types or methods for which ContainsGenericParameters is true

Sergey Mirvoda
+2  A: 

Well, yes, that doesn't behave very gracefully. It is partly fixed in .NET 4.0, the CreateDelegate() method returns null. That still doesn't behave gracefully when the JIT optimizer is enabled though, it assumes that CreateDelegate cannot return null and doesn't perform a null check. You'll get a FatalExecutionEngineError instead of NRE, an exception that is no longer catchable in 4.0

I'd recommend you report the defect to connect.microsoft.com. Not so sure they'll take you seriously, I don't know what their policy is when you intentionally bypass the safeguards. It could well be that they consider the FEEE good enough. The workaround is obvious, only call methods of concrete types.

Hans Passant
+1  A: 

I don't think that the code you posted actually works under the .net framework vers. 2.0 to 4.0. Static methods can be called AFAIK only on closed generic types, not on opened generic types. So you'll have to provide a type parameter. As for the debugger, try to launch your process outside Visual Studio and then attach the VS-Debugger. This time you'll get the expected exception right away.

Marcel Roma
>>"So you'll have to provide a type parameter" Yep. I have to provide type parameter. I didn't. I've forgotten. Or I even didn't know I deal with generic Type. And the code works.
Andrew_B