Implement a C# Interface in Python for .NET
Asked Answered
T

1

11

I have been given a library written in C# that I'm trying to call using Python for .NET.

The primary class I need an instance of has a constructor like:

GDhuClient(IGDhuSettings)

There are no (exposed) classes that implement the IGDhuSettings interface. When I create a Python class to implement it, e.g.,

class PyGDhuSettings(IGDhuSettings):
    ...

I get TypeError: interface takes exactly one argument if I don't have a __new__ method or if I define one the normal way:

def __new__(cls):
    return super().__new__(cls)

If I try to instantiate the interface as if it were a class, I either get the same error (with no or >1 arguments) or <whatever> does not implement IGDhuSettings if I pass it a single argument.

Looking at the Python for .NET source,

using System;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Python.Runtime
{
    /// <summary>
    /// Provides the implementation for reflected interface types. Managed
    /// interfaces are represented in Python by actual Python type objects.
    /// Each of those type objects is associated with an instance of this
    /// class, which provides the implementation for the Python type.
    /// </summary>
    internal class InterfaceObject : ClassBase
    {
        internal ConstructorInfo ctor;

        internal InterfaceObject(Type tp) : base(tp)
        {
            var coclass = (CoClassAttribute)Attribute.GetCustomAttribute(tp, cc_attr);
            if (coclass != null)
            {
                ctor = coclass.CoClass.GetConstructor(Type.EmptyTypes);
            }
        }

        private static Type cc_attr;

        static InterfaceObject()
        {
            cc_attr = typeof(CoClassAttribute);
        }

        /// <summary>
        /// Implements __new__ for reflected interface types.
        /// </summary>
        public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw)
        {
            var self = (InterfaceObject)GetManagedObject(tp);
            int nargs = Runtime.PyTuple_Size(args);
            Type type = self.type;
            object obj;

            if (nargs == 1)
            {
                IntPtr inst = Runtime.PyTuple_GetItem(args, 0);
                var co = GetManagedObject(inst) as CLRObject;

                if (co == null || !type.IsInstanceOfType(co.inst))
                {
                    Exceptions.SetError(Exceptions.TypeError, $"object does not implement {type.Name}");
                    return IntPtr.Zero;
                }

                obj = co.inst;
            }

            else if (nargs == 0 && self.ctor != null)
            {
                obj = self.ctor.Invoke(null);

                if (obj == null || !type.IsInstanceOfType(obj))
                {
                    Exceptions.SetError(Exceptions.TypeError, "CoClass default constructor failed");
                    return IntPtr.Zero;
                }
            }

            else
            {
                Exceptions.SetError(Exceptions.TypeError, "interface takes exactly one argument");
                return IntPtr.Zero;
            }

            return CLRObject.GetInstHandle(obj, self.pyHandle);
        }
    }
}

I don't see a means of implementing a C# interface in Python without either a CoClass (there isn't one defined) or already having a class that implements it.

Is there some nuance that I'm missing here, or is this a limitation of Python for .NET?

Discussion on GitHub: https://github.com/pythonnet/pythonnet/issues/674

Tedious answered 9/4, 2018 at 15:27 Comment(1)
I haven’t used Python for .NET for anything serious, but I could see how this may be an intentional limitation. Your Python code doesn’t run in .NET, so it would have to build a proxy method for each method of the interface, which could be slow and fragile, so maybe they want you to either revamp your design so you can just pass a delegate, or use IronPython instead?Downes
K
20

I found that I needed to add the __namespace__ field to the Python class implementing the interface. As far as I understand, it this field with represent the .NET namespace for your Python class that implements the interface.

As an example. I have made a C# library with the following interface:

public interface ITestInterface
{
    string StringAdd(string text, int number);
}

and a trivial static function that consumes the interface:

public static string InvokeStringAdd(ITestInterface testInterface, string text)
{
    return "*** " + testInterface.StringAdd(text, 7) + " ***";
}

Then on the Python side I define the following class implementing the interface:

class TestInterfaceSubClass(ITestInterface):
    __namespace__ = "MyNameSpace"
    
    def StringAdd(self,text,x):
        return 'My text <{}> with number <{}>'.format(text, x)

Note that I added the __namespace__ field and give it the "MyNameSpace" value. I think any non-colliding .NET namespace name would do.

To test the implementation in Python

test_interface_subclass = TestInterfaceSubClass()
print(TestClass.InvokeStringAdd(test_interface_subclass,'My Text'))

which returns

*** My text <My Text> with number <7> ***

Finally note, that the python class TestInterfaceSubClass is given the namespace "MyNameSpace". This means that reevaluation the class definition code in e.g a Jupyter notebook will result in a namepsace clash on the .NET side, so you either have to give it a different namespace value or restart the kernel.

Kala answered 22/5, 2018 at 8:3 Comment(1)
I was almost pulling my hair to create events and whatnot on my dll's so I could use an Implementation on python! saved my life :)Riffraff

© 2022 - 2024 — McMap. All rights reserved.