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.