12

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

1
  • 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? Commented Apr 9, 2018 at 16:13

1 Answer 1

24

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.