🌐 AI搜索 & 代理 主页
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 42 additions & 22 deletions src/runtime/Types/ClassDerived.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,15 @@ internal static Type CreateDerivedType(string name,

// Override any properties explicitly overridden in python
var pyProperties = new HashSet<string>();
var dictKeys = new HashSet<string>();
if (py_dict != null && Runtime.PyDict_Check(py_dict))
{
using var dict = new PyDict(py_dict);
using var keys = dict.Keys();
foreach (PyObject pyKey in keys)
{
var keyString = pyKey.As<string>();
dictKeys.Add(keyString);
using var value = dict[pyKey];
if (value.HasAttr("_clr_property_type_"))
{
Expand Down Expand Up @@ -239,11 +242,18 @@ internal static Type CreateDerivedType(string name,
continue;
}

// if the name of the method is not in the dict keys, then the method is not explicitly
// declared in the python code and we dont need to add it here.
bool isDeclared = dictKeys.Contains(method.Name);
if (!isDeclared)
continue;

// keep track of the virtual methods redirected to the python instance
virtualMethods.Add(method.Name);


// override the virtual method to call out to the python method, if there is one.
AddVirtualMethod(method, baseType, typeBuilder);
AddVirtualMethod(method, typeBuilder);
}

// Add any additional methods and properties explicitly exposed from Python.
Expand Down Expand Up @@ -271,35 +281,43 @@ internal static Type CreateDerivedType(string name,
}
}

// add the destructor so the python object created in the constructor gets destroyed
MethodBuilder methodBuilder = typeBuilder.DefineMethod("Finalize",
MethodAttributes.Family |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
CallingConventions.Standard,
typeof(void),
Type.EmptyTypes);
ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);

// only add finalizer if it has not allready been added on a base type.
// otherwise PyFinalize will be called multiple times for the same object,
// causing an access violation exception on some platforms.
// to see if this is the case, we can check if the base type is a IPythonDerivedType if so, it already
// has the finalizer.
if (typeof(IPythonDerivedType).IsAssignableFrom(baseType) == false)
{
// add the destructor so the python object created in the constructor gets destroyed
MethodBuilder methodBuilder = typeBuilder.DefineMethod("Finalize",
MethodAttributes.Family |
MethodAttributes.Virtual |
MethodAttributes.HideBySig,
CallingConventions.Standard,
typeof(void),
Type.EmptyTypes);
ILGenerator il = methodBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(PyFinalize)));
il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(PyFinalize)));
#pragma warning restore CS0618 // PythonDerivedType is for internal use only
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseClass.GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance));
il.Emit(OpCodes.Ret);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, baseClass.GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance));
il.Emit(OpCodes.Ret);
}

Type type = typeBuilder.CreateType();

// scan the assembly so the newly added class can be imported
// scan the assembly so the newly added class can be imported.
Assembly assembly = Assembly.GetAssembly(type);
AssemblyManager.ScanAssembly(assembly);

// FIXME: assemblyBuilder not used
AssemblyBuilder assemblyBuilder = assemblyBuilders[assemblyName];

return type;
}



/// <summary>
/// Add a constructor override that calls the python ctor after calling the base type constructor.
/// </summary>
Expand Down Expand Up @@ -366,9 +384,8 @@ private static void AddConstructor(ConstructorInfo ctor, Type baseType, TypeBuil
/// and calls it, otherwise fall back to the base class method.
/// </summary>
/// <param name="method">virtual method to be overridden</param>
/// <param name="baseType">Python callable object</param>
/// <param name="typeBuilder">TypeBuilder for the new type the method is to be added to</param>
private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuilder typeBuilder)
private static void AddVirtualMethod(MethodInfo method, TypeBuilder typeBuilder)
{
ParameterInfo[] parameters = method.GetParameters();
Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray();
Expand All @@ -377,7 +394,7 @@ private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuild
string? baseMethodName = null;
if (!method.IsAbstract)
{
baseMethodName = "_" + baseType.Name + "__" + method.Name;
baseMethodName = "_" + method.DeclaringType.Name + "__" + method.Name;
MethodBuilder baseMethodBuilder = typeBuilder.DefineMethod(baseMethodName,
MethodAttributes.Public |
MethodAttributes.Final |
Expand Down Expand Up @@ -720,12 +737,15 @@ public class PythonDerivedType
{
var disposeList = new List<PyObject>();
PyGILState gs = Runtime.PyGILState_Ensure();


try
{
using var pyself = new PyObject(self.CheckRun());
using PyObject method = pyself.GetAttr(methodName, Runtime.None);
if (method.Reference != Runtime.PyNone)
{

// if the method hasn't been overridden then it will be a managed object
ManagedType? managedMethod = ManagedType.GetManagedObject(method.Reference);
if (null == managedMethod)
Expand Down
10 changes: 10 additions & 0 deletions src/testing/classtest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,15 @@ public static void TestObject(object obj)
throw new Exception("Expected ISayHello and SimpleClass instance");
}
}

public virtual string SayGoodbye()
{
return "!";
}
}

public class SimpleClass2 : SimpleClass
{
// this class does not override SayGoodbye.
}
}
38 changes: 36 additions & 2 deletions tests/test_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import System
import pytest
from Python.Test import (IInterfaceTest, SubClassTest, EventArgsTest,
FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, ISayHello1)
FunctionsTest, IGenericInterface, GenericVirtualMethodTest, SimpleClass, SimpleClass2, ISayHello1)
from System.Collections.Generic import List


Expand Down Expand Up @@ -339,8 +339,42 @@ class OverloadingSubclass2(OverloadingSubclass):
assert obj.VirtMethod[int](5) == 5

def test_implement_interface_and_class():
import clr
class DualSubClass0(ISayHello1, SimpleClass):
__namespace__ = "Test0"
def SayHello(self):
return "hello"

@clr.clrmethod(str, [])
def SayGoodbye(self):
return "bye" + super().SayGoodbye()

def test_multi_level_subclass():
"""
Test multi levels of subclassing. This has shown verious issues, like stack overflow
exception if a method was not implemented in the middle of the tree.
"""
import clr
class DualSubClass0(ISayHello1, SimpleClass2):
__namespace__ = "Test"
def SayHello(self):
return "hello"
obj = DualSubClass0()
def SayGoodbye(self):
return "bye" + super().SayGoodbye()
class DualSubClass1(DualSubClass0):
__namespace__ = "Test"
def SayHello(self):
return super().SayHello() + " hi1"
class DualSubClass2(DualSubClass1):
__namespace__ = "Test"
class DualSubClass3(DualSubClass2):
__namespace__ = "Test"
def SayHello(self):
return super().SayHello() + " hi3"
def SayGoodbye(self):
return super().SayGoodbye() + "!"
obj = DualSubClass3()
helloResult = obj.SayHello()
goodByeResult = obj.SayGoodbye()
assert goodByeResult =="bye!!"
assert helloResult == "hello hi1 hi3"
Loading