RSCG – NativeObjects
| name | NativeObjects |
| nuget | https://www.nuget.org/packages/NativeObjects/ |
| link | https://github.com/kevingosse/NativeObjects |
| author | Kevin Gosse |
Object to IntPtr and back
This is how you can use NativeObjects .
The code that you start with is
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> <ItemGroup> <PackageReference Include="NativeObjects" Version="1.3.0" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> <PropertyGroup> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath> </PropertyGroup> </Project>
The code that you will use is
using NativeObjectsDemo;
Person p = new Person();
p.DateOfBirth= new DateTime(1970,4,16);
using (var nativ = NativeObjects.IPerson.Wrap(p))
{
SomeNativeCode((IntPtr)nativ.Object);
}
static void SomeNativeCode(IntPtr nativePerson)
{
var p = NativeObjects.IPerson.Wrap(nativePerson);
Console.WriteLine($"Age: {p.CalculateAge()}");
}
namespace NativeObjectsDemo;
[NativeObject]
public interface IPerson
{
public int CalculateAge();
}
class Person : IPerson
{
public DateTime DateOfBirth { get; set; }
public int CalculateAge()
{
return (int)DateTime.Now.Subtract(DateOfBirth).TotalDays / 365;
}
}
The code that is generated is
using System;
[AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
internal class NativeObjectAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = false)]
internal class NativeObjectsNamespaceAttribute : Attribute
{
public NativeObjectsNamespaceAttribute(string name) { }
}
namespace NativeObjects
{
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
public unsafe class IPerson : IDisposable
{
private IPerson(NativeObjectsDemo.IPerson implementation)
{
const int delegateCount = 1;
var vtable = (IntPtr*)NativeMemory.Alloc((nuint)delegateCount, (nuint)IntPtr.Size);
*(vtable + 0) = (IntPtr)(delegate* unmanaged<IntPtr*, int>)&Exports.CalculateAge;
var obj = (IntPtr*)NativeMemory.Alloc((nuint)2, (nuint)IntPtr.Size);
*obj = (IntPtr)vtable;
var handle = GCHandle.Alloc(implementation);
*(obj + 1) = GCHandle.ToIntPtr(handle);
Object = (IntPtr)obj;
}
public IntPtr Object { get; private set; }
public static IPerson Wrap(NativeObjectsDemo.IPerson implementation) => new(implementation);
public static IPersonInvoker Wrap(IntPtr ptr) => new(ptr);
public static implicit operator IntPtr(IPerson stub) => stub.Object;
public void Dispose()
{
if (Object != IntPtr.Zero)
{
var target = (void**)Object;
NativeMemory.Free(*target);
NativeMemory.Free(target);
Object = IntPtr.Zero;
}
}
private static class Exports
{
[UnmanagedCallersOnly]
public static int CalculateAge(IntPtr* self)
{
var handle = GCHandle.FromIntPtr(*(self + 1));
var obj = (NativeObjectsDemo.IPerson)handle.Target;
var result = obj.CalculateAge();
return result;
}
}
}
public unsafe struct IPersonInvoker
{
private readonly IntPtr _implementation;
public IPersonInvoker(IntPtr implementation)
{
_implementation = implementation;
}
public static implicit operator IntPtr(IPersonInvoker invoker) => invoker._implementation;
private nint* VTable => (nint*)*(nint*)_implementation;
public int CalculateAge()
{
var __func__ = (delegate* unmanaged[Stdcall]<IntPtr, int>)*(VTable + 0);
return __func__(_implementation);
}
}
}
Code and pdf at
https://ignatandrei.github.io/RSCG_Examples/v2/docs/NativeObjects