libmir / mir.net

Mir Ref-Counted Type System for .NET

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Can it wrap native class (methods)?

Superbelko opened this issue · comments

I've seen this library was mentioned somewhere on D forums in a fashion like it can be used to wrap native class.

So my questions:
Is it possible to wrap unmanaged class?
And what about allocations?
How to destroy objects?
Is there a better way to wrap this code?

My use case is basically wrap some D code (mix of classes/structs/free functions) to use in C#, possibly extend with inheritance.

Code:

I've slapped some basic test code, on D side there is a simple C++ class with 3 virtual and 1 final methods and no members, there is also factory function to create this one, this class is then loaded on .NET side and called directly using delegates.

// dtest.d
// don't forget to add dependency `dub add mir-algorithm`
import std.stdio;

import mir.utility;
import mir.rc;

export extern(C++) class MyClass 
{
    export void doA() { writeln("hello"); }
    export int doB() { return 42; }
    export int doC(int val) { return 2*val; }
    export final float doFinal() { return 1.5; }

    final ~this() { writeln("~this() called"); }

    pragma(msg, doA.mangleof);
    pragma(msg, doB.mangleof);
    pragma(msg, doC.mangleof);
    pragma(msg, doFinal.mangleof);
}

export extern(C) RCPtr!MyClass makeObj() {
    writeln("makeObj()");
    return createRC!MyClass();
}

version(BuildLibrary)
{
    version(Windows)
    {
        import core.sys.windows.windows;
        import core.sys.windows.dll;
        mixin SimpleDllMain;
    }
}
else
{
    void main()
    {
        auto c = createRC!MyClass();
        c.doA();
    }
}

And the followind C# code, it kind of works and I can wrap it further with some scripting so it can be inherited

// app.cs
// dependencies: `dotnet add package mir`
using System;
using System.Runtime.InteropServices;

using Mir;
using Handle = Mir.Native.Handle;

namespace classtest
{
    public interface MyClass
    {
        [DllImport("dtest", CallingConvention = CallingConvention.Cdecl)]
        public static extern Handle.RCPtr makeObj();

        [DllImport("dtest", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?doA@MyClass@@UEAAXXZ")]
        public static extern void _doA(Handle.RCPtr _this);

        [DllImport("dtest", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?doB@MyClass@@UEAAHXZ")]
        public static extern int _doB(Handle.RCPtr _this);

        [DllImport("dtest", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?doC@MyClass@@UEAAHH@Z")]
        public static extern int _doC(Handle.RCPtr _this, int val);

        [DllImport("dtest", CallingConvention = CallingConvention.ThisCall, EntryPoint = "?doFinal@MyClass@@QEAAMXZ")]
        public static extern float _doFinal(Handle.RCPtr _this);
    }

    
    class Program
    {
        static void Main(string[] args)
        {
            var cls = MyClass.makeObj();

            if (cls.Ptr == IntPtr.Zero)
                throw new NullReferenceException("makeObj failed");

            MyClass._doA(cls);

            var b = MyClass._doB(cls);
            Console.WriteLine(b);

            var c = MyClass._doC(cls, 4);
            Console.WriteLine(c);

            MyClass._doFinal(cls);
            var f = MyClass._doFinal(cls);
            Console.WriteLine(f);
        }
    }
}

Is it possible to wrap unmanaged class?

Yes

And what about allocations?

They are automatic (and require D creation function for classes but not for structs). And looks simply like new MyClass on C# side.

How to destroy objects?

They are destroyed when the reference count is decreased to 0. The reference count is decreased when the C# object is finalized.

Is there a better way to wrap this code?

Yes. It is a bit buggy. For example, extern(C) function shouldn't return ref-counted structs/objects. Also the class C# should be inherited from MirPtr or MirSlimPtr.

I will rework it at the end of this week and add it to the CI.

Ok, thanks. I'll try to mess a bit with existing code, but having an example for reference certainly will be helpful.

@Superbelko Do you need an example with inheritance support or plain pointers?

As you've mentioned earlier, simply deriving from MirWrapper seems to make factory function unnecessary(or I could be totally wrong since in such basic example there is no internal state that could mess up with ABI), however I haven't figured out myself how to call methods on wrapper implementation.

So it would be awesome if you provided some demo case with native classes, including methods and data access.