diff --git a/src/runtime/classderived.cs b/src/runtime/classderived.cs index 685becef9..baffbd4e3 100644 --- a/src/runtime/classderived.cs +++ b/src/runtime/classderived.cs @@ -321,37 +321,47 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild ParameterInfo[] parameters = method.GetParameters(); Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray(); - // create a method for calling the original method - string baseMethodName = "_" + baseType.Name + "__" + method.Name; - MethodBuilder methodBuilder = typeBuilder.DefineMethod(baseMethodName, - MethodAttributes.Public | - MethodAttributes.Final | - MethodAttributes.HideBySig, - method.ReturnType, - parameterTypes); - - // emit the assembly for calling the original method using call instead of callvirt - ILGenerator il = methodBuilder.GetILGenerator(); - il.Emit(OpCodes.Ldarg_0); - for (int i = 0; i < parameters.Length; ++i) - il.Emit(OpCodes.Ldarg, i + 1); - il.Emit(OpCodes.Call, method); - il.Emit(OpCodes.Ret); + // If the method isn't abstract create a method for calling the original method + string baseMethodName = null; + if (!method.IsAbstract) + { + baseMethodName = "_" + baseType.Name + "__" + method.Name; + MethodBuilder baseMethodBuilder = typeBuilder.DefineMethod(baseMethodName, + MethodAttributes.Public | + MethodAttributes.Final | + MethodAttributes.HideBySig, + method.ReturnType, + parameterTypes); + + // emit the assembly for calling the original method using call instead of callvirt + ILGenerator baseIl = baseMethodBuilder.GetILGenerator(); + baseIl.Emit(OpCodes.Ldarg_0); + for (int i = 0; i < parameters.Length; ++i) + baseIl.Emit(OpCodes.Ldarg, i + 1); + baseIl.Emit(OpCodes.Call, method); + baseIl.Emit(OpCodes.Ret); + } // override the original method with a new one that dispatches to python - methodBuilder = typeBuilder.DefineMethod(method.Name, - MethodAttributes.Public | - MethodAttributes.ReuseSlot | - MethodAttributes.Virtual | - MethodAttributes.HideBySig, - method.CallingConvention, - method.ReturnType, - parameterTypes); - il = methodBuilder.GetILGenerator(); + MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name, + MethodAttributes.Public | + MethodAttributes.ReuseSlot | + MethodAttributes.Virtual | + MethodAttributes.HideBySig, + method.CallingConvention, + method.ReturnType, + parameterTypes); + ILGenerator il = methodBuilder.GetILGenerator(); il.DeclareLocal(typeof(Object[])); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldstr, method.Name); - il.Emit(OpCodes.Ldstr, baseMethodName); + + // don't fall back to the base type's method if it's abstract + if (null != baseMethodName) + il.Emit(OpCodes.Ldstr, baseMethodName); + else + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Ldc_I4, parameters.Length); il.Emit(OpCodes.Newarr, typeof(System.Object)); il.Emit(OpCodes.Stloc_0); @@ -624,7 +634,7 @@ public static T InvokeMethod(IPythonDerivedType obj, string methodName, strin } if (origMethodName == null) - throw new NullReferenceException("Python object does not have a '" + methodName + "' method"); + throw new NotImplementedException("Python object does not have a '" + methodName + "' method"); return (T)obj.GetType().InvokeMember(origMethodName, BindingFlags.InvokeMethod, @@ -683,7 +693,7 @@ public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, s } if (origMethodName == null) - throw new NullReferenceException("Python object does not have a '" + methodName + "' method"); + throw new NotImplementedException("Python object does not have a '" + methodName + "' method"); obj.GetType().InvokeMember(origMethodName, BindingFlags.InvokeMethod, diff --git a/src/testing/subclasstest.cs b/src/testing/subclasstest.cs index 64cea87c6..7a68c6dca 100644 --- a/src/testing/subclasstest.cs +++ b/src/testing/subclasstest.cs @@ -12,10 +12,17 @@ public interface IInterfaceTest // test passing objects and boxing primitives string bar(string s, int i); + + // test events on interfaces + event TestEventHandler TestEvent; + + void OnTestEvent(int value); } public class SubClassTest : IInterfaceTest { + public event TestEventHandler TestEvent; + public SubClassTest() { } @@ -48,6 +55,13 @@ public static IList test_list(SubClassTest x) // calls into python if return_list is overriden return x.return_list(); } + + // raise the test event + public virtual void OnTestEvent(int value) + { + if (null != TestEvent) + TestEvent(this, new TestEventArgs(value)); + } } public class TestFunctions @@ -75,5 +89,18 @@ public static IInterfaceTest pass_through(IInterfaceTest s) { return s; } + + public static int test_event(IInterfaceTest x, int value) + { + // reuse the event handler from eventtest.cs + EventTest et = new EventTest(); + x.TestEvent += et.GenericHandler; + + // raise the event (should trigger both python and managed handlers) + x.OnTestEvent(value); + + x.TestEvent -= et.GenericHandler; + return et.value; + } } } diff --git a/src/tests/test_subclass.py b/src/tests/test_subclass.py index 113397dba..f116eb4de 100644 --- a/src/tests/test_subclass.py +++ b/src/tests/test_subclass.py @@ -11,8 +11,9 @@ clr.AddReference('System') import sys, os, string, unittest, types -from Python.Test import TestFunctions, SubClassTest, IInterfaceTest +from Python.Test import TestFunctions, SubClassTest, IInterfaceTest, TestEventArgs from System.Collections.Generic import List +from System import NotImplementedException # class that implements the test interface class InterfaceTestClass(IInterfaceTest): @@ -47,6 +48,26 @@ def return_list(self): l.Add("C") return l +# class that implements IInterfaceTest.TestEvent +class DerivedEventTest(IInterfaceTest): + __namespace__ = "Python.Test" + + def __init__(self): + self.event_handlers = [] + + # event handling + def add_TestEvent(self, handler): + self.event_handlers.append(handler) + + def remove_TestEvent(self, handler): + self.event_handlers.remove(handler) + + def OnTestEvent(self, value): + args = TestEventArgs(value) + for handler in self.event_handlers: + handler(self, args) + + class SubClassTests(unittest.TestCase): """Test subclassing managed types""" @@ -109,6 +130,29 @@ def testCreateInstance(self): y = TestFunctions.pass_through(object2) self.assertEqual(id(y), id(object2)) + def testEvents(self): + + class EventHandler: + def handler(self, x, args): + self.value = args.value + + event_handler = EventHandler() + + x = SubClassTest() + x.TestEvent += event_handler.handler + self.assertEqual(TestFunctions.test_event(x, 1), 1) + self.assertEqual(event_handler.value, 1) + + i = InterfaceTestClass() + self.assertRaises(NotImplementedException, TestFunctions.test_event, i, 2) + + d = DerivedEventTest() + d.add_TestEvent(event_handler.handler) + self.assertEqual(TestFunctions.test_event(d, 3), 3) + self.assertEqual(event_handler.value, 3) + self.assertEqual(len(d.event_handlers), 1) + + def test_suite(): return unittest.makeSuite(SubClassTests)